diff --git a/CHANGELOG.md b/CHANGELOG.md index 6929e41a..774c8507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,14 +16,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * The implementation for `IntoPy for T` where `U: FromPy` is no longer specializable. Control the behavior of this via the implementation of `FromPy`. [#713](https://github.com/PyO3/pyo3/pull/713) * Use `parking_lot::Mutex` instead of `spin::Mutex`. [#734](https://github.com/PyO3/pyo3/pull/734) * Bumped minimum Rust version to `1.42.0-nightly 2020-01-21`. [#761](https://github.com/PyO3/pyo3/pull/761) +* `PyRef` and `PyRefMut` are renewed for `PyCell`. [#770](https://github.com/PyO3/pyo3/pull/770) ### Added - -* `PyClass`, `PyClassShell`, `PyObjectLayout`, `PyClassInitializer`. [#683](https://github.com/PyO3/pyo3/pull/683) +* `PyCell`, which has RefCell-like features. [#770](https://github.com/PyO3/pyo3/pull/770) +* `PyClass`, `PyLayout`, `PyClassInitializer`. [#683](https://github.com/PyO3/pyo3/pull/683) * Implemented `IntoIterator` for `PySet` and `PyFrozenSet`. [#716](https://github.com/PyO3/pyo3/pull/716) * `FromPyObject` is now automatically implemented for `T: Clone` pyclasses. [#730](https://github.com/PyO3/pyo3/pull/730) * `#[pyo3(get)]` and `#[pyo3(set)]` will now use the Rust doc-comment from the field for the Python property. [#755](https://github.com/PyO3/pyo3/pull/755) * `#[setter]` functions may now take an argument of `Pyo3::Python`. [#760](https://github.com/PyO3/pyo3/pull/760) +* `PyTypeInfo::BaseLayout` and `PyClass::BaseNativeType`. [#770](https://github.com/PyO3/pyo3/pull/770) +* `PyDowncastImpl`. [#770](https://github.com/PyO3/pyo3/pull/770) ### Fixed @@ -35,9 +38,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Removed -* `PyRef`, `PyRefMut`, `PyRawObject`. [#683](https://github.com/PyO3/pyo3/pull/683) +* `PyRawObject`. [#683](https://github.com/PyO3/pyo3/pull/683) * `PyNoArgsFunction`. [#741](https://github.com/PyO3/pyo3/pull/741) * `initialize_type()`. To set the module name for a `#[pyclass]`, use the `module` argument to the macro. #[751](https://github.com/PyO3/pyo3/pull/751) +* `AsPyRef::as_mut/with/with_mut/into_py/into_mut_py`. [#770](https://github.com/PyO3/pyo3/pull/770) +* `PyTryFrom::try_from_mut/try_from_mut_exact/try_from_mut_unchecked`. [#770](https://github.com/PyO3/pyo3/pull/770) +* `Python::mut_from_owned_ptr/mut_from_borrowed_ptr`. [#770](https://github.com/PyO3/pyo3/pull/770) +* `ObjectProtocol::get_base/get_mut_base`. [#770](https://github.com/PyO3/pyo3/pull/770) ## [0.8.5] diff --git a/guide/src/class.md b/guide/src/class.md index 9215c6a9..b5bf071d 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -22,6 +22,7 @@ Specifically, the following implementation is generated: ```rust use pyo3::prelude::*; +use pyo3::types::PyAny; /// Class for demonstration struct MyClass { @@ -33,9 +34,10 @@ impl pyo3::pyclass::PyClassAlloc for MyClass {} unsafe impl pyo3::PyTypeInfo for MyClass { type Type = MyClass; - type BaseType = pyo3::types::PyAny; - type ConcreteLayout = pyo3::PyClassShell; - type Initializer = pyo3::PyClassInitializer; + type BaseType = PyAny; + type BaseLayout = pyo3::pycell::PyCellBase; + type Layout = PyCell; + type Initializer = PyClassInitializer; const NAME: &'static str = "MyClass"; const MODULE: Option<&'static str> = None; @@ -53,6 +55,7 @@ unsafe impl pyo3::PyTypeInfo for MyClass { impl pyo3::pyclass::PyClass for MyClass { type Dict = pyo3::pyclass_slots::PyClassDummySlot; type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; + type BaseNativeType = PyAny; } impl pyo3::IntoPy for MyClass { @@ -105,47 +108,56 @@ fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> { } ``` -## Get Python objects from `pyclass` -You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it). +## PyCell and interior mutability +You sometimes need to convert your `pyclass` into a Python object and access it +from Rust code (e.g., for testing it). +[`PyCell`](https://pyo3.rs/master/doc/pyo3/pycell/struct.PyCell.html) is our primary interface for that. -For getting *GIL-bounded* (i.e., with `'py` lifetime) references of `pyclass`, -you can use `PyClassShell`. -Or you can use `Py` directly, for *not-GIL-bounded* references. +`PyCell` is always allocated in the Python heap, so we don't have the ownership of it. +We can only get `&PyCell`, not `PyCell`. -### `PyClassShell` -`PyClassShell` represents the actual layout of `pyclass` on the Python heap. +Thus, to mutate data behind `&PyCell` safely, we employ the +[Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html) +like [std::cell::RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html). -If you want to instantiate `pyclass` in Python and get the reference, -you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. +Users who are familiar with `RefCell` can use `PyCell` just like `RefCell`. + +For users who are not very familiar with `RefCell`, here is a reminder of Rust's rules of borrowing: +- At any given time, you can have either (but not both of) one mutable reference or any number of immutable references. +- References must always be valid. +`PyCell` ensures these borrowing rules by tracking references at runtime. ```rust # use pyo3::prelude::*; # use pyo3::types::PyDict; -# use pyo3::PyClassShell; #[pyclass] struct MyClass { + #[pyo3(get)] num: i32, debug: bool, } let gil = Python::acquire_gil(); let py = gil.python(); -let obj = PyClassShell::new_ref(py, MyClass { num: 3, debug: true }).unwrap(); -// You can use deref -assert_eq!(obj.num, 3); -let dict = PyDict::new(py); -// You can treat a `&PyClassShell` as a normal Python object -dict.set_item("obj", obj).unwrap(); - -// return &mut PyClassShell -let obj = PyClassShell::new_mut(py, MyClass { num: 3, debug: true }).unwrap(); -obj.num = 5; +let obj = PyCell::new(py, MyClass { num: 3, debug: true }).unwrap(); +{ + let obj_ref = obj.borrow(); // Get PyRef + assert_eq!(obj_ref.num, 3); + // You cannot get PyRefMut unless all PyRefs are dropped + assert!(obj.try_borrow_mut().is_err()); +} +{ + let mut obj_mut = obj.borrow_mut(); // Get PyRefMut + obj_mut.num = 5; + // You cannot get any other refs until the PyRefMut is dropped + assert!(obj.try_borrow().is_err()); + assert!(obj.try_borrow_mut().is_err()); +} +// You can convert `&PyCell` to Python object +pyo3::py_run!(py, obj, "assert obj.num == 5") ``` -### `Py` - -`Py` is an object wrapper which stores an object longer than the GIL lifetime. - -You can use it to avoid lifetime problems. +`&PyCell` is bounded by the same lifetime as `GILGuard`. +To avoid this you can use `Py`, which stores an object longer than the GIL lifetime. ```rust # use pyo3::prelude::*; #[pyclass] @@ -159,7 +171,9 @@ fn return_myclass() -> Py { } let gil = Python::acquire_gil(); let obj = return_myclass(); -assert_eq!(obj.as_ref(gil.python()).num, 1); +let cell = obj.as_ref(gil.python()); // AsPyRef::as_ref returns &PyCell +let obj_ref = cell.borrow(); // Get PyRef +assert_eq!(obj_ref.num, 1); ``` ## Customizing the class @@ -228,9 +242,14 @@ baseclass of `T`. But for more deeply nested inheritance, you have to return `PyClassInitializer` explicitly. +To get a parent class from a child, use `PyRef` instead of `&self`, +or `PyRefMut` instead of `&mut self`. +Then you can access a parent class by `self_.as_ref()` as `&Self::BaseClass`, +or by `self_.into_super()` as `PyRef`. + ```rust # use pyo3::prelude::*; -use pyo3::PyClassShell; +use pyo3::PyCell; #[pyclass] struct BaseClass { @@ -261,8 +280,9 @@ impl SubClass { (SubClass{ val2: 15}, BaseClass::new()) } - fn method2(self_: &PyClassShell) -> PyResult { - self_.get_super().method().map(|x| x * self_.val2) + fn method2(self_: PyRef) -> PyResult { + let super_ = self_.as_ref(); // Get &BaseClass + super_.method().map(|x| x * self_.val2) } } @@ -279,29 +299,24 @@ impl SubSubClass { .add_subclass(SubSubClass{val3: 20}) } - fn method3(self_: &PyClassShell) -> PyResult { - let super_ = self_.get_super(); - SubClass::method2(super_).map(|x| x * self_.val3) + fn method3(self_: PyRef) -> PyResult { + let v = self_.val3; + let super_ = self_.into_super(); // Get PyRef + SubClass::method2(super_).map(|x| x * v) } } # let gil = Python::acquire_gil(); # let py = gil.python(); -# let subsub = pyo3::PyClassShell::new_ref(py, SubSubClass::new()).unwrap(); +# let subsub = pyo3::PyCell::new(py, SubSubClass::new()).unwrap(); # pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000") ``` -To access the super class, you can use either of these two ways: -- Use `self_: &PyClassShell` instead of `self`, and call `get_super()` -- `ObjectProtocol::get_base` -We recommend `PyClassShell` here, since it makes the context much clearer. - - If `SubClass` does not provide a baseclass initialization, the compilation fails. ```compile_fail # use pyo3::prelude::*; -use pyo3::PyClassShell; +use pyo3::PyCell; #[pyclass] struct BaseClass { @@ -761,8 +776,8 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol Iterators can be defined using the [`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait. It includes two methods `__iter__` and `__next__`: - * `fn __iter__(slf: &mut PyClassShell) -> PyResult>` - * `fn __next__(slf: &mut PyClassShell) -> PyResult>>` + * `fn __iter__(slf: PyRefMut) -> PyResult>` + * `fn __next__(slf: PyRefMut) -> PyResult>>` Returning `Ok(None)` from `__next__` indicates that that there are no further items. @@ -770,7 +785,7 @@ Example: ```rust use pyo3::prelude::*; -use pyo3::{PyIterProtocol, PyClassShell}; +use pyo3::{PyIterProtocol, PyCell}; #[pyclass] struct MyIterator { @@ -779,19 +794,15 @@ struct MyIterator { #[pyproto] impl PyIterProtocol for MyIterator { - fn __iter__(slf: &mut PyClassShell) -> PyResult> { + fn __iter__(mut slf: PyRefMut) -> PyResult> { Ok(slf.into()) } - fn __next__(slf: &mut PyClassShell) -> PyResult> { + fn __next__(mut slf: PyRefMut) -> PyResult> { Ok(slf.iter.next()) } } ``` -## Manually implementing pyclass - -TODO: Which traits to implement (basically `PyTypeCreate: PyObjectAlloc + PyTypeInfo + PyMethodsProtocol + Sized`) and what they mean. - ## How methods are implemented Users should be able to define a `#[pyclass]` with or without `#[pymethods]`, while PyO3 needs a diff --git a/guide/src/python_from_rust.md b/guide/src/python_from_rust.md index c20a3281..1582a6b7 100644 --- a/guide/src/python_from_rust.md +++ b/guide/src/python_from_rust.md @@ -36,7 +36,7 @@ your Python extensions quickly. ```rust use pyo3::prelude::*; -use pyo3::{PyClassShell, PyObjectProtocol, py_run}; +use pyo3::{PyCell, PyObjectProtocol, py_run}; # fn main() { #[pyclass] struct UserData { @@ -61,7 +61,7 @@ let userdata = UserData { id: 34, name: "Yu".to_string(), }; -let userdata = PyClassShell::new_ref(py, userdata).unwrap(); +let userdata = PyCell::new(py, userdata).unwrap(); let userdata_as_tuple = (34, "Yu"); py_run!(py, userdata userdata_as_tuple, r#" assert repr(userdata) == "User Yu(id: 34)" diff --git a/pyo3-derive-backend/src/method.rs b/pyo3-derive-backend/src/method.rs index ae0186ca..8042ce11 100644 --- a/pyo3-derive-backend/src/method.rs +++ b/pyo3-derive-backend/src/method.rs @@ -29,12 +29,16 @@ pub enum FnType { FnCall, FnClass, FnStatic, - PySelf(syn::TypeReference), + /// For methods taht have `self_: &PyCell` instead of self receiver + PySelfRef(syn::TypeReference), + /// For methods taht have `self_: PyRef` or `PyRefMut` instead of self receiver + PySelfPath(syn::TypePath), } #[derive(Clone, PartialEq, Debug)] pub struct FnSpec<'a> { pub tp: FnType, + pub self_: Option, // Rust function name pub name: &'a syn::Ident, // Wrapped python name. This should not have any leading r#. @@ -54,6 +58,14 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { } impl<'a> FnSpec<'a> { + /// Generate the code for borrowing self + pub(crate) fn borrow_self(&self) -> TokenStream { + let is_mut = self + .self_ + .expect("impl_borrow_self is called for non-self fn"); + crate::utils::borrow_self(is_mut, true) + } + /// Parser function signature and function attributes pub fn parse( sig: &'a syn::Signature, @@ -67,19 +79,19 @@ impl<'a> FnSpec<'a> { mut python_name, } = parse_method_attributes(meth_attrs, allow_custom_name)?; - let mut has_self = false; + let mut self_ = None; let mut arguments = Vec::new(); for input in sig.inputs.iter() { match input { - syn::FnArg::Receiver(_) => { - has_self = true; + syn::FnArg::Receiver(recv) => { + self_ = Some(recv.mutability.is_some()); } syn::FnArg::Typed(syn::PatType { ref pat, ref ty, .. }) => { // skip first argument (cls) - if fn_type == FnType::FnClass && !has_self { - has_self = true; + if fn_type == FnType::FnClass && self_.is_none() { + self_ = Some(false); continue; } @@ -114,18 +126,18 @@ impl<'a> FnSpec<'a> { let ty = get_return_info(&sig.output); - if fn_type == FnType::Fn && !has_self { + if fn_type == FnType::Fn && self_.is_none() { if arguments.is_empty() { return Err(syn::Error::new_spanned( name, "Static method needs #[staticmethod] attribute", )); } - let tp = match arguments.remove(0).ty { - syn::Type::Reference(r) => replace_self(r)?, + fn_type = match arguments.remove(0).ty { + syn::Type::Reference(r) => FnType::PySelfRef(replace_self_in_ref(r)?), + syn::Type::Path(p) => FnType::PySelfPath(replace_self_in_path(p)), x => return Err(syn::Error::new_spanned(x, "Invalid type as custom self")), }; - fn_type = FnType::PySelf(tp); } // "Tweak" getter / setter names: strip off set_ and get_ if needed @@ -158,9 +170,11 @@ impl<'a> FnSpec<'a> { }; let text_signature = match &fn_type { - FnType::Fn | FnType::PySelf(_) | FnType::FnClass | FnType::FnStatic => { - utils::parse_text_signature_attrs(&mut *meth_attrs, name)? - } + FnType::Fn + | FnType::PySelfRef(_) + | FnType::PySelfPath(_) + | FnType::FnClass + | FnType::FnStatic => utils::parse_text_signature_attrs(&mut *meth_attrs, name)?, FnType::FnNew => parse_erroneous_text_signature( "text_signature not allowed on __new__; if you want to add a signature on \ __new__, put it on the struct definition instead", @@ -174,6 +188,7 @@ impl<'a> FnSpec<'a> { Ok(FnSpec { tp: fn_type, + self_, name, python_name, attrs: fn_attrs, @@ -514,17 +529,24 @@ fn parse_method_name_attribute( } // Replace &A with &A<_> -fn replace_self(refn: &syn::TypeReference) -> syn::Result { - fn infer(span: proc_macro2::Span) -> syn::GenericArgument { - syn::GenericArgument::Type(syn::Type::Infer(syn::TypeInfer { - underscore_token: syn::token::Underscore { spans: [span] }, - })) - } +fn replace_self_in_ref(refn: &syn::TypeReference) -> syn::Result { let mut res = refn.to_owned(); let tp = match &mut *res.elem { syn::Type::Path(p) => p, _ => return Err(syn::Error::new_spanned(refn, "unsupported argument")), }; + replace_self_impl(tp); + res.lifetime = None; + Ok(res) +} + +fn replace_self_in_path(tp: &syn::TypePath) -> syn::TypePath { + let mut res = tp.to_owned(); + replace_self_impl(&mut res); + res +} + +fn replace_self_impl(tp: &mut syn::TypePath) { for seg in &mut tp.path.segments { if let syn::PathArguments::AngleBracketed(ref mut g) = seg.arguments { let mut args = syn::punctuated::Punctuated::new(); @@ -546,6 +568,9 @@ fn replace_self(refn: &syn::TypeReference) -> syn::Result { g.args = args; } } - res.lifetime = None; - Ok(res) + fn infer(span: proc_macro2::Span) -> syn::GenericArgument { + syn::GenericArgument::Type(syn::Type::Infer(syn::TypeInfer { + underscore_token: syn::token::Underscore { spans: [span] }, + })) + } } diff --git a/pyo3-derive-backend/src/module.rs b/pyo3-derive-backend/src/module.rs index badaf37f..975b0574 100644 --- a/pyo3-derive-backend/src/module.rs +++ b/pyo3-derive-backend/src/module.rs @@ -28,7 +28,7 @@ pub fn py_init(fnname: &Ident, name: &Ident, doc: syn::LitStr) -> TokenStream { } /// Finds and takes care of the #[pyfn(...)] in `#[pymodule]` -pub fn process_functions_in_module(func: &mut syn::ItemFn) { +pub fn process_functions_in_module(func: &mut syn::ItemFn) -> syn::Result<()> { let mut stmts: Vec = Vec::new(); for stmt in func.block.stmts.iter_mut() { @@ -36,7 +36,7 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) { if let Some((module_name, python_name, pyfn_attrs)) = extract_pyfn_attrs(&mut func.attrs) { - let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs); + let function_to_python = add_fn_to_module(func, python_name, pyfn_attrs)?; let function_wrapper_ident = function_wrapper_ident(&func.sig.ident); let item: syn::ItemFn = syn::parse_quote! { fn block_wrapper() { @@ -51,31 +51,27 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) { } func.block.stmts = stmts; + Ok(()) } /// Transforms a rust fn arg parsed with syn into a method::FnArg -fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a Ident) -> Option> { - match input { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(ref cap) => { - let (mutability, by_ref, ident) = match *cap.pat { - syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident), - _ => panic!("unsupported argument: {:?}", cap.pat), - }; +fn wrap_fn_argument<'a>(cap: &'a syn::PatType, name: &'a Ident) -> syn::Result> { + let (mutability, by_ref, ident) = match *cap.pat { + syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident), + _ => return Err(syn::Error::new_spanned(&cap.pat, "Unsupported argument")), + }; - let py = crate::utils::if_type_is_python(&cap.ty); - let opt = method::check_arg_ty_and_optional(&name, &cap.ty); - Some(method::FnArg { - name: ident, - mutability, - by_ref, - ty: &cap.ty, - optional: opt, - py, - reference: method::is_ref(&name, &cap.ty), - }) - } - } + let py = crate::utils::if_type_is_python(&cap.ty); + let opt = method::check_arg_ty_and_optional(&name, &cap.ty); + Ok(method::FnArg { + name: ident, + mutability, + by_ref, + ty: &cap.ty, + optional: opt, + py, + reference: method::is_ref(&name, &cap.ty), + }) } /// Extracts the data from the #[pyfn(...)] attribute of a function @@ -136,30 +132,31 @@ pub fn add_fn_to_module( func: &mut syn::ItemFn, python_name: Ident, pyfn_attrs: Vec, -) -> TokenStream { +) -> syn::Result { let mut arguments = Vec::new(); + let mut self_ = None; for input in func.sig.inputs.iter() { - if let Some(fn_arg) = wrap_fn_argument(input, &func.sig.ident) { - arguments.push(fn_arg); + match input { + syn::FnArg::Receiver(recv) => { + self_ = Some(recv.mutability.is_some()); + } + syn::FnArg::Typed(ref cap) => { + arguments.push(wrap_fn_argument(cap, &func.sig.ident)?); + } } } let ty = method::get_return_info(&func.sig.output); - let text_signature = match utils::parse_text_signature_attrs(&mut func.attrs, &python_name) { - Ok(text_signature) => text_signature, - Err(err) => return err.to_compile_error(), - }; - let doc = match utils::get_doc(&func.attrs, text_signature, true) { - Ok(doc) => doc, - Err(err) => return err.to_compile_error(), - }; + let text_signature = utils::parse_text_signature_attrs(&mut func.attrs, &python_name)?; + let doc = utils::get_doc(&func.attrs, text_signature, true)?; let function_wrapper_ident = function_wrapper_ident(&func.sig.ident); let spec = method::FnSpec { tp: method::FnType::Fn, + self_, name: &function_wrapper_ident, python_name, attrs: pyfn_attrs, @@ -174,7 +171,7 @@ pub fn add_fn_to_module( let wrapper = function_c_wrapper(&func.sig.ident, &spec); - let tokens = quote! { + Ok(quote! { fn #function_wrapper_ident(py: pyo3::Python) -> pyo3::PyObject { #wrapper @@ -197,9 +194,7 @@ pub fn add_fn_to_module( function } - }; - - tokens + }) } /// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords) @@ -226,8 +221,7 @@ fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>) -> TokenStream { #body - pyo3::callback::cb_convert( - pyo3::callback::PyObjectCallbackConverter, _py, _result) + pyo3::callback::cb_obj_convert(_py, _result) } } } diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 688d9a31..a36bcd2f 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -317,14 +317,18 @@ fn impl_class( } let weakref = if has_weakref { - quote! { type WeakRef = pyo3::pyclass_slots::PyClassWeakRefSlot; } + quote! { pyo3::pyclass_slots::PyClassWeakRefSlot } + } else if attr.has_extends { + quote! { ::WeakRef } } else { - quote! { type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; } + quote! { pyo3::pyclass_slots::PyClassDummySlot } }; let dict = if has_dict { - quote! { type Dict = pyo3::pyclass_slots::PyClassDictSlot; } + quote! { pyo3::pyclass_slots::PyClassDictSlot } + } else if attr.has_extends { + quote! { ::Dict } } else { - quote! { type Dict = pyo3::pyclass_slots::PyClassDummySlot; } + quote! { pyo3::pyclass_slots::PyClassDummySlot } }; let module = if let Some(m) = &attr.module { quote! { Some(#m) } @@ -357,6 +361,16 @@ fn impl_class( } else { quote! { 0 } }; + let base_layout = if attr.has_extends { + quote! { ::LayoutAsBase } + } else { + quote! { pyo3::pycell::PyCellBase } + }; + let base_nativetype = if attr.has_extends { + quote! { ::BaseNativeType } + } else { + quote! { pyo3::types::PyAny } + }; // If #cls is not extended type, we allow Self->PyObject conversion let into_pyobject = if !attr.has_extends { @@ -375,7 +389,8 @@ fn impl_class( unsafe impl pyo3::type_object::PyTypeInfo for #cls { type Type = #cls; type BaseType = #base; - type ConcreteLayout = pyo3::pyclass::PyClassShell; + type Layout = pyo3::pycell::PyCell; + type BaseLayout = #base_layout; type Initializer = pyo3::pyclass_init::PyClassInitializer; const NAME: &'static str = #cls_name; @@ -392,20 +407,9 @@ fn impl_class( } impl pyo3::PyClass for #cls { - #dict - #weakref - } - - impl pyo3::conversion::FromPyObjectImpl for #cls { - type Impl = pyo3::conversion::extract_impl::Cloned; - } - - impl pyo3::conversion::FromPyObjectImpl for &'_ #cls { - type Impl = pyo3::conversion::extract_impl::Reference; - } - - impl pyo3::conversion::FromPyObjectImpl for &'_ mut #cls { - type Impl = pyo3::conversion::extract_impl::MutReference; + type Dict = #dict; + type WeakRef = #weakref; + type BaseNativeType = #base_nativetype; } #into_pyobject diff --git a/pyo3-derive-backend/src/pyfunction.rs b/pyo3-derive-backend/src/pyfunction.rs index 588deb6a..c7bcacd3 100644 --- a/pyo3-derive-backend/src/pyfunction.rs +++ b/pyo3-derive-backend/src/pyfunction.rs @@ -234,7 +234,7 @@ pub fn parse_name_attribute(attrs: &mut Vec) -> syn::Result syn::Result { let python_name = parse_name_attribute(&mut ast.attrs)?.unwrap_or_else(|| ast.sig.ident.unraw()); - Ok(add_fn_to_module(ast, python_name, args.arguments)) + add_fn_to_module(ast, python_name, args.arguments) } #[cfg(test)] diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index 5827f43c..0449c204 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -20,7 +20,10 @@ pub fn gen_py_method( Ok(match spec.tp { FnType::Fn => impl_py_method_def(&spec, &impl_wrap(cls, &spec, true)), - FnType::PySelf(ref self_ty) => { + FnType::PySelfRef(ref self_ty) => { + impl_py_method_def(&spec, &impl_wrap_pyslf(cls, &spec, self_ty, true)) + } + FnType::PySelfPath(ref self_ty) => { impl_py_method_def(&spec, &impl_wrap_pyslf(cls, &spec, self_ty, true)) } FnType::FnNew => impl_py_method_def_new(&spec, &impl_wrap_new(cls, &spec)), @@ -59,14 +62,18 @@ fn check_generic(sig: &syn::Signature) -> syn::Result<()> { /// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap(cls: &syn::Type, spec: &FnSpec<'_>, noargs: bool) -> TokenStream { let body = impl_call(cls, &spec); - let slf = impl_self("e! { &mut #cls }); + let borrow_self = spec.borrow_self(); + let slf = quote! { + let _slf = _py.from_borrowed_ptr::>(_slf); + #borrow_self + }; impl_wrap_common(cls, spec, noargs, slf, body) } pub fn impl_wrap_pyslf( cls: &syn::Type, spec: &FnSpec<'_>, - self_ty: &syn::TypeReference, + self_ty: impl quote::ToTokens, noargs: bool, ) -> TokenStream { let names = get_arg_names(spec); @@ -74,7 +81,13 @@ pub fn impl_wrap_pyslf( let body = quote! { #cls::#name(_slf, #(#names),*) }; - let slf = impl_self(self_ty); + let slf = quote! { + let _cell = _py.from_borrowed_ptr::>(_slf); + let _slf: #self_ty = match std::convert::TryFrom::try_from(_cell) { + Ok(_slf) => _slf, + Err(e) => return pyo3::PyErr::from(e).restore_and_null(_py), + }; + }; impl_wrap_common(cls, spec, noargs, slf, body) } @@ -102,8 +115,7 @@ fn impl_wrap_common( pyo3::derive_utils::IntoPyResult::into_py_result(#body) }; - pyo3::callback::cb_convert( - pyo3::callback::PyObjectCallbackConverter, _py, _result) + pyo3::callback::cb_obj_convert(_py, _result) } } } else { @@ -125,8 +137,7 @@ fn impl_wrap_common( #body - pyo3::callback::cb_convert( - pyo3::callback::PyObjectCallbackConverter, _py, _result) + pyo3::callback::cb_obj_convert(_py, _result) } } } @@ -137,6 +148,7 @@ pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { let python_name = &spec.python_name; let cb = impl_call(cls, &spec); let body = impl_arg_params(&spec, cb); + let borrow_self = spec.borrow_self(); quote! { #[allow(unused_mut)] @@ -148,14 +160,14 @@ pub fn impl_proto_wrap(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); - let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf); + let _slf = _py.from_borrowed_ptr::>(_slf); + #borrow_self let _args = _py.from_borrowed_ptr::(_args); let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); #body - pyo3::callback::cb_convert( - pyo3::callback::PyObjectCallbackConverter, _py, _result) + pyo3::callback::cb_obj_convert(_py, _result) } } } @@ -189,7 +201,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { #body - match _result.and_then(|init| pyo3::PyClassInitializer::from(init).create_shell(_py)) { + match _result.and_then(|init| pyo3::PyClassInitializer::from(init).create_cell(_py)) { Ok(slf) => slf as _, Err(e) => e.restore_and_null(_py), } @@ -222,8 +234,7 @@ pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { #body - pyo3::callback::cb_convert( - pyo3::callback::PyObjectCallbackConverter, _py, _result) + pyo3::callback::cb_obj_convert(_py, _result) } } } @@ -252,8 +263,7 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { #body - pyo3::callback::cb_convert( - pyo3::callback::PyObjectCallbackConverter, _py, _result) + pyo3::callback::cb_obj_convert(_py, _result) } } } @@ -273,7 +283,6 @@ fn impl_call_getter(spec: &FnSpec) -> syn::Result { } else { quote! { _slf.#name() } }; - Ok(fncall) } @@ -296,6 +305,7 @@ pub(crate) fn impl_wrap_getter( PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_getter(&spec)?), }; + let borrow_self = crate::utils::borrow_self(false, true); Ok(quote! { unsafe extern "C" fn __wrap( _slf: *mut pyo3::ffi::PyObject, _: *mut ::std::os::raw::c_void) -> *mut pyo3::ffi::PyObject @@ -304,18 +314,14 @@ pub(crate) fn impl_wrap_getter( let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); - let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf); + let _slf = _py.from_borrowed_ptr::>(_slf); + #borrow_self let result = pyo3::derive_utils::IntoPyResult::into_py_result(#getter_impl); match result { - Ok(val) => { - pyo3::IntoPyPointer::into_ptr(pyo3::IntoPy::::into_py(val, _py)) - } - Err(e) => { - e.restore(_py); - ::std::ptr::null_mut() - } + Ok(val) => pyo3::IntoPyPointer::into_ptr(pyo3::IntoPy::::into_py(val, _py)), + Err(e) => e.restore_and_null(_py), } } }) @@ -359,6 +365,7 @@ pub(crate) fn impl_wrap_setter( PropertyType::Function(spec) => (spec.python_name.clone(), impl_call_setter(&spec)?), }; + let borrow_self = crate::utils::borrow_self(true, false); Ok(quote! { #[allow(unused_mut)] unsafe extern "C" fn __wrap( @@ -368,7 +375,8 @@ pub(crate) fn impl_wrap_setter( const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); - let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf); + let _slf = _py.from_borrowed_ptr::>(_slf); + #borrow_self let _value = _py.from_borrowed_ptr(_value); let _result = match pyo3::FromPyObject::extract(_value) { @@ -401,12 +409,6 @@ fn impl_call(_cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { quote! { _slf.#fname(#(#names),*) } } -fn impl_self(self_ty: &T) -> TokenStream { - quote! { - let _slf: #self_ty = pyo3::FromPyPointer::from_borrowed_ptr(_py, _slf); - } -} - /// Converts a bool to "true" or "false" fn bool_to_ident(condition: bool) -> syn::Ident { if condition { diff --git a/pyo3-derive-backend/src/utils.rs b/pyo3-derive-backend/src/utils.rs index a06c31ff..32b66b25 100644 --- a/pyo3-derive-backend/src/utils.rs +++ b/pyo3-derive-backend/src/utils.rs @@ -1,9 +1,32 @@ // Copyright (c) 2017-present PyO3 Project and Contributors - use proc_macro2::Span; use proc_macro2::TokenStream; +use quote::quote; use std::fmt::Display; +pub(crate) fn borrow_self(is_mut: bool, return_null: bool) -> TokenStream { + let ret = if return_null { + quote! { restore_and_null } + } else { + quote! { restore_and_minus1 } + }; + if is_mut { + quote! { + let mut _slf = match _slf.try_borrow_mut() { + Ok(ref_) => ref_, + Err(e) => return pyo3::PyErr::from(e).#ret(_py), + }; + } + } else { + quote! { + let _slf = match _slf.try_borrow() { + Ok(ref_) => ref_, + Err(e) => return pyo3::PyErr::from(e).#ret(_py), + }; + } + } +} + pub fn print_err(msg: String, t: TokenStream) { println!("Error: {} in '{}'", msg, t.to_string()); } diff --git a/pyo3cls/src/lib.rs b/pyo3cls/src/lib.rs index 0ba93c99..795423cb 100644 --- a/pyo3cls/src/lib.rs +++ b/pyo3cls/src/lib.rs @@ -23,7 +23,9 @@ pub fn pymodule(attr: TokenStream, input: TokenStream) -> TokenStream { parse_macro_input!(attr as syn::Ident) }; - process_functions_in_module(&mut ast); + if let Err(err) = process_functions_in_module(&mut ast) { + return err.to_compile_error().into(); + } let doc = match get_doc(&ast.attrs, None, false) { Ok(doc) => doc, diff --git a/src/callback.rs b/src/callback.rs index 8061aeea..edeca66c 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -10,53 +10,64 @@ use crate::{IntoPy, PyObject, Python}; use std::os::raw::c_int; use std::{isize, ptr}; -pub trait CallbackConverter { - type R; +/// Convert the result of callback function into the appropriate return value. +/// +/// Used by PyO3 macros. +pub trait CallbackConverter { + type Source; + type Result: Copy; + const ERR_VALUE: Self::Result; - fn convert(s: S, p: Python) -> Self::R; - fn error_value() -> Self::R; -} - -pub struct PyObjectCallbackConverter; - -impl CallbackConverter for PyObjectCallbackConverter -where - S: IntoPy, -{ - type R = *mut ffi::PyObject; - - fn convert(val: S, py: Python) -> *mut ffi::PyObject { - val.into_py(py).into_ptr() - } + fn convert(s: Self::Source, py: Python) -> Self::Result; #[inline] - fn error_value() -> *mut ffi::PyObject { - ptr::null_mut() + fn convert_result(py: Python, value: PyResult) -> Self::Result { + match value { + Ok(val) => Self::convert(val, py), + Err(e) => { + e.restore(py); + Self::ERR_VALUE + } + } + } +} + +pub struct PyObjectCallbackConverter(pub std::marker::PhantomData); + +impl CallbackConverter for PyObjectCallbackConverter +where + T: IntoPy, +{ + type Source = T; + type Result = *mut ffi::PyObject; + const ERR_VALUE: Self::Result = ptr::null_mut(); + + fn convert(s: Self::Source, py: Python) -> Self::Result { + s.into_py(py).into_ptr() } } pub struct BoolCallbackConverter; -impl CallbackConverter for BoolCallbackConverter { - type R = c_int; +impl CallbackConverter for BoolCallbackConverter { + type Source = bool; + type Result = c_int; + const ERR_VALUE: Self::Result = -1; #[inline] - fn convert(val: bool, _py: Python) -> c_int { - val as c_int - } - - #[inline] - fn error_value() -> c_int { - -1 + fn convert(s: Self::Source, _py: Python) -> Self::Result { + s as c_int } } pub struct LenResultConverter; -impl CallbackConverter for LenResultConverter { - type R = isize; +impl CallbackConverter for LenResultConverter { + type Source = usize; + type Result = isize; + const ERR_VALUE: Self::Result = -1; - fn convert(val: usize, py: Python) -> isize { + fn convert(val: Self::Source, py: Python) -> Self::Result { if val <= (isize::MAX as usize) { val as isize } else { @@ -64,27 +75,19 @@ impl CallbackConverter for LenResultConverter { -1 } } - - #[inline] - fn error_value() -> isize { - -1 - } } pub struct UnitCallbackConverter; -impl CallbackConverter<()> for UnitCallbackConverter { - type R = c_int; +impl CallbackConverter for UnitCallbackConverter { + type Source = (); + type Result = c_int; + const ERR_VALUE: Self::Result = -1; #[inline] - fn convert(_: (), _: Python) -> c_int { + fn convert(_s: Self::Source, _py: Python) -> Self::Result { 0 } - - #[inline] - fn error_value() -> c_int { - -1 - } } pub trait WrappingCastTo { @@ -112,13 +115,15 @@ wrapping_cast!(i32, Py_hash_t); wrapping_cast!(isize, Py_hash_t); wrapping_cast!(i64, Py_hash_t); -pub struct HashConverter; +pub struct HashConverter(pub std::marker::PhantomData); -impl CallbackConverter for HashConverter +impl CallbackConverter for HashConverter where T: WrappingCastTo, { - type R = Py_hash_t; + type Source = T; + type Result = Py_hash_t; + const ERR_VALUE: Self::Result = -1; #[inline] fn convert(val: T, _py: Python) -> Py_hash_t { @@ -129,23 +134,31 @@ where hash } } +} - #[inline] - fn error_value() -> Py_hash_t { - -1 - } +// Short hands methods for macros +#[inline] +pub fn cb_convert(_c: C, py: Python, value: PyResult) -> C::Result +where + C: CallbackConverter, +{ + C::convert_result(py, value) +} + +// Same as cb_convert(PyObjectCallbackConverter, py, value) +#[inline] +pub fn cb_obj_convert>( + py: Python, + value: PyResult, +) -> as CallbackConverter>::Result { + PyObjectCallbackConverter::::convert_result(py, value) } #[inline] -pub unsafe fn cb_convert(_c: C, py: Python, value: PyResult) -> C::R +pub unsafe fn cb_err(_c: C, py: Python, err: impl Into) -> C::Result where - C: CallbackConverter, + C: CallbackConverter, { - match value { - Ok(val) => C::convert(val, py), - Err(e) => { - e.restore(py); - C::error_value() - } - } + err.into().restore(py); + C::ERR_VALUE } diff --git a/src/class/basic.rs b/src/class/basic.rs index 8c670bf8..d6be61f2 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -11,16 +11,10 @@ use crate::callback::{BoolCallbackConverter, HashConverter, PyObjectCallbackConverter}; use crate::class::methods::PyMethodDef; use crate::err::{PyErr, PyResult}; -use crate::ffi; use crate::objectprotocol::ObjectProtocol; -use crate::type_object::PyTypeInfo; use crate::types::PyAny; -use crate::FromPyObject; -use crate::IntoPyPointer; -use crate::Python; -use crate::{exceptions, IntoPy, PyObject}; +use crate::{exceptions, ffi, FromPyObject, IntoPy, IntoPyPointer, PyClass, PyObject, Python}; use std::os::raw::c_int; -use std::ptr; /// Operators for the __richcmp__ method #[derive(Debug)] @@ -35,7 +29,7 @@ pub enum CompareOp { /// Basic python class customization #[allow(unused_variables)] -pub trait PyObjectProtocol<'p>: PyTypeInfo { +pub trait PyObjectProtocol<'p>: PyClass { fn __getattr__(&'p self, name: Self::Name) -> Self::Result where Self: PyObjectGetAttrProtocol<'p>, @@ -237,14 +231,15 @@ where return existing; } - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::>(slf); let arg = py.from_borrowed_ptr::(arg); - - let result = match arg.extract() { - Ok(arg) => slf.__getattr__(arg).into(), - Err(e) => Err(e), - }; - crate::callback::cb_convert(PyObjectCallbackConverter, py, result) + call_ref_with_converter!( + slf, + PyObjectCallbackConverter::(std::marker::PhantomData), + py, + __getattr__, + arg + ) } Some(wrap::) } @@ -365,8 +360,7 @@ where py_unary_func!( PyObjectStrProtocol, T::__str__, - ::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -390,8 +384,7 @@ where py_unary_func!( PyObjectReprProtocol, T::__repr__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -454,8 +447,7 @@ where py_unary_func!( PyObjectHashProtocol, T::__hash__, - isize, - HashConverter, + HashConverter::(std::marker::PhantomData), ffi::Py_hash_t ) } @@ -480,7 +472,6 @@ where py_unary_func!( PyObjectBoolProtocol, T::__bool__, - bool, BoolCallbackConverter, c_int ) @@ -513,22 +504,24 @@ where { let py = Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = py.from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::>(slf); let arg = py.from_borrowed_ptr::(arg); - let res = match extract_op(op) { - Ok(op) => match arg.extract() { - Ok(arg) => slf.__richcmp__(arg, op).into(), - Err(e) => Err(e), - }, - Err(e) => Err(e), - }; - match res { - Ok(val) => val.into_py(py).into_ptr(), - Err(e) => { - e.restore(py); - ptr::null_mut() + match slf.try_borrow() { + Ok(borrowed_slf) => { + let res = match extract_op(op) { + Ok(op) => match arg.extract() { + Ok(arg) => borrowed_slf.__richcmp__(arg, op).into(), + Err(e) => Err(e), + }, + Err(e) => Err(e), + }; + match res { + Ok(val) => val.into_py(py).into_ptr(), + Err(e) => e.restore_and_null(py), + } } + Err(e) => PyErr::from(e).restore_and_null(py), } } Some(wrap::) diff --git a/src/class/buffer.rs b/src/class/buffer.rs index 04dd21e1..54e496a1 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -6,8 +6,7 @@ //! c-api use crate::callback::UnitCallbackConverter; use crate::err::PyResult; -use crate::ffi; -use crate::{PyClass, PyClassShell}; +use crate::{ffi, PyClass, PyRefMut}; use std::os::raw::c_int; /// Buffer protocol interface @@ -16,18 +15,14 @@ use std::os::raw::c_int; /// c-api #[allow(unused_variables)] pub trait PyBufferProtocol<'p>: PyClass { - fn bf_getbuffer( - slf: &mut PyClassShell, - view: *mut ffi::Py_buffer, - flags: c_int, - ) -> Self::Result + fn bf_getbuffer(slf: PyRefMut, view: *mut ffi::Py_buffer, flags: c_int) -> Self::Result where Self: PyBufferGetBufferProtocol<'p>, { unimplemented!() } - fn bf_releasebuffer(slf: &mut PyClassShell, view: *mut ffi::Py_buffer) -> Self::Result + fn bf_releasebuffer(slf: PyRefMut, view: *mut ffi::Py_buffer) -> Self::Result where Self: PyBufferReleaseBufferProtocol<'p>, { @@ -98,9 +93,11 @@ where { let py = crate::Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = &mut *(slf as *mut PyClassShell); - - let result = T::bf_getbuffer(slf, arg1, arg2).into(); + let slf = py.from_borrowed_ptr::>(slf); + let result = slf + .try_borrow_mut() + .map_err(crate::PyErr::from) + .and_then(|slf_mut| T::bf_getbuffer(slf_mut, arg1, arg2).into()); crate::callback::cb_convert(UnitCallbackConverter, py, result) } Some(wrap::) @@ -132,9 +129,11 @@ where { let py = crate::Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = &mut *(slf as *mut PyClassShell); - - let result = T::bf_releasebuffer(slf, arg1).into(); + let slf = py.from_borrowed_ptr::>(slf); + let result = slf + .try_borrow_mut() + .map_err(crate::PyErr::from) + .and_then(|slf_mut| T::bf_releasebuffer(slf_mut, arg1).into()); crate::callback::cb_convert(UnitCallbackConverter, py, result); } Some(wrap::) diff --git a/src/class/context.rs b/src/class/context.rs index 50aea33e..9a9e0e98 100644 --- a/src/class/context.rs +++ b/src/class/context.rs @@ -6,12 +6,11 @@ use crate::class::methods::PyMethodDef; use crate::err::PyResult; -use crate::type_object::PyTypeInfo; -use crate::PyObject; +use crate::{PyClass, PyObject}; /// Context manager interface #[allow(unused_variables)] -pub trait PyContextProtocol<'p>: PyTypeInfo { +pub trait PyContextProtocol<'p>: PyClass { fn __enter__(&'p mut self) -> Self::Result where Self: PyContextEnterProtocol<'p>, diff --git a/src/class/descr.rs b/src/class/descr.rs index 57d9119c..61c2e51c 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -8,15 +8,13 @@ use crate::callback::{PyObjectCallbackConverter, UnitCallbackConverter}; use crate::class::methods::PyMethodDef; use crate::err::PyResult; -use crate::type_object::PyTypeInfo; use crate::types::{PyAny, PyType}; -use crate::FromPyObject; -use crate::{ffi, IntoPy, PyObject}; +use crate::{ffi, FromPyObject, IntoPy, PyClass, PyObject}; use std::os::raw::c_int; /// Descriptor interface #[allow(unused_variables)] -pub trait PyDescrProtocol<'p>: PyTypeInfo { +pub trait PyDescrProtocol<'p>: PyClass { fn __get__(&'p self, instance: &'p PyAny, owner: Option<&'p PyType>) -> Self::Result where Self: PyDescrGetProtocol<'p>, @@ -89,8 +87,7 @@ where py_ternary_func!( PyDescrGetProtocol, T::__get__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -111,13 +108,7 @@ where T: for<'p> PyDescrSetProtocol<'p>, { fn tp_descr_set() -> Option { - py_ternary_func!( - PyDescrSetProtocol, - T::__set__, - (), - UnitCallbackConverter, - c_int - ) + py_ternary_func!(PyDescrSetProtocol, T::__set__, UnitCallbackConverter, c_int) } } diff --git a/src/class/gc.rs b/src/class/gc.rs index 591b686a..b8638808 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,17 +3,14 @@ //! Python GC support //! -use crate::ffi; -use crate::type_object::PyTypeInfo; -use crate::AsPyPointer; -use crate::Python; +use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; use std::os::raw::{c_int, c_void}; #[repr(transparent)] pub struct PyTraverseError(c_int); /// GC support -pub trait PyGCProtocol<'p>: PyTypeInfo { +pub trait PyGCProtocol<'p>: PyClass { fn __traverse__(&'p self, visit: PyVisit) -> Result<(), PyTraverseError>; fn __clear__(&'p mut self); } @@ -94,14 +91,14 @@ where { let py = Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::>(slf); let visit = PyVisit { visit, arg, _py: py, }; - match slf.__traverse__(visit) { + match slf.borrow().__traverse__(visit) { Ok(()) => 0, Err(PyTraverseError(code)) => code, } @@ -136,9 +133,9 @@ where { let py = Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::>(slf); - slf.__clear__(); + slf.borrow_mut().__clear__(); 0 } Some(tp_clear::) diff --git a/src/class/iter.rs b/src/class/iter.rs index 802bd159..8d7522e3 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -4,24 +4,23 @@ use crate::callback::{CallbackConverter, PyObjectCallbackConverter}; use crate::err::PyResult; -use crate::{ffi, pyclass::PyClassShell, IntoPy, PyClass, PyObject}; -use crate::{IntoPyPointer, Python}; +use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, PyRefMut, Python}; use std::ptr; /// Python Iterator Interface. /// -/// more information -/// `https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_iter` +/// Check [CPython doc](https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_iter) +/// for more. #[allow(unused_variables)] pub trait PyIterProtocol<'p>: PyClass { - fn __iter__(slf: &mut PyClassShell) -> Self::Result + fn __iter__(slf: PyRefMut) -> Self::Result where Self: PyIterIterProtocol<'p>, { unimplemented!() } - fn __next__(slf: &mut PyClassShell) -> Self::Result + fn __next__(slf: PyRefMut) -> Self::Result where Self: PyIterNextProtocol<'p>, { @@ -78,11 +77,10 @@ where { #[inline] fn tp_iter() -> Option { - py_unary_pyref_func!( + py_unary_refmut_func!( PyIterIterProtocol, T::__iter__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -106,24 +104,25 @@ where { #[inline] fn tp_iternext() -> Option { - py_unary_pyref_func!( + py_unary_refmut_func!( PyIterNextProtocol, T::__next__, - Option, - IterNextConverter + IterNextConverter::(std::marker::PhantomData) ) } } -struct IterNextConverter; +struct IterNextConverter(std::marker::PhantomData); -impl CallbackConverter> for IterNextConverter +impl CallbackConverter for IterNextConverter where T: IntoPy, { - type R = *mut ffi::PyObject; + type Source = Option; + type Result = *mut ffi::PyObject; + const ERR_VALUE: Self::Result = ptr::null_mut(); - fn convert(val: Option, py: Python) -> *mut ffi::PyObject { + fn convert(val: Self::Source, py: Python) -> Self::Result { match val { Some(val) => val.into_py(py).into_ptr(), None => unsafe { @@ -132,9 +131,4 @@ where }, } } - - #[inline] - fn error_value() -> *mut ffi::PyObject { - ptr::null_mut() - } } diff --git a/src/class/macros.rs b/src/class/macros.rs index 7fbf2499..7cb9ae7e 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -3,25 +3,33 @@ #[macro_export] #[doc(hidden)] macro_rules! py_unary_func { - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => { + ($trait:ident, $class:ident :: $f:ident, $conv: expr) => { + py_unary_func!($trait, $class::$f, $conv, *mut $crate::ffi::PyObject); + }; + // Use call_ref! by default + ($trait:ident, $class:ident :: $f:ident, $conv: expr, $ret_type:ty) => { py_unary_func!( $trait, $class::$f, - $res_type, $conv, - *mut $crate::ffi::PyObject + $ret_type, + call_ref_with_converter ); }; - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr, $ret_type:ty) => {{ + ($trait: ident, + $class:ident :: $f:ident, + $conv: expr, + $ret_type: ty, + $call: ident + ) => {{ unsafe extern "C" fn wrap(slf: *mut $crate::ffi::PyObject) -> $ret_type where T: for<'p> $trait<'p>, { let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); - let res = slf.$f().into(); - $crate::callback::cb_convert($conv, py, res.map(|x| x)) + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); + $call!(slf, $conv, py, $f) } Some(wrap::<$class>) }}; @@ -29,17 +37,16 @@ macro_rules! py_unary_func { #[macro_export] #[doc(hidden)] -macro_rules! py_unary_pyref_func { - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => {{ +macro_rules! py_unary_refmut_func { + ($trait:ident, $class:ident :: $f:ident, $conv:expr) => {{ unsafe extern "C" fn wrap(slf: *mut $crate::ffi::PyObject) -> *mut $crate::ffi::PyObject where T: for<'p> $trait<'p>, { - use $crate::pyclass::PyClassShell; let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf: &mut PyClassShell = &mut *(slf as *mut PyClassShell); - let res = $class::$f(slf).into(); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); + let res = $class::$f(slf.borrow_mut()).into(); $crate::callback::cb_convert($conv, py, res) } Some(wrap::<$class>) @@ -56,9 +63,8 @@ macro_rules! py_len_func { { let py = Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); - - let result = slf.$f().into(); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); + let result = call_ref!(slf, $f); $crate::callback::cb_convert($conv, py, result) } Some(wrap::<$class>) @@ -68,17 +74,14 @@ macro_rules! py_len_func { #[macro_export] #[doc(hidden)] macro_rules! py_binary_func { - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => { - py_binary_func!( - $trait, - $class::$f, - $res_type, - $conv, - *mut $crate::ffi::PyObject - ) + ($trait:ident, $class:ident :: $f:ident, $conv:expr) => { + py_binary_func!($trait, $class::$f, $conv, *mut $crate::ffi::PyObject) }; - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr, $return:ty) => {{ - #[allow(unused_mut)] + // Use call_ref! by default + ($trait:ident, $class:ident :: $f:ident, $conv:expr, $return:ty) => {{ + py_binary_func!($trait, $class::$f, $conv, $return, call_ref_with_converter) + }}; + ($trait:ident, $class:ident :: $f:ident, $conv:expr, $return:ty, $call:ident) => {{ unsafe extern "C" fn wrap(slf: *mut ffi::PyObject, arg: *mut ffi::PyObject) -> $return where T: for<'p> $trait<'p>, @@ -86,14 +89,9 @@ macro_rules! py_binary_func { use $crate::ObjectProtocol; let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg = py.from_borrowed_ptr::<$crate::types::PyAny>(arg); - - let result = match arg.extract() { - Ok(arg) => slf.$f(arg).into(), - Err(e) => Err(e.into()), - }; - $crate::callback::cb_convert($conv, py, result) + $call!(slf, $conv, py, $f, arg) } Some(wrap::<$class>) }}; @@ -102,8 +100,7 @@ macro_rules! py_binary_func { #[macro_export] #[doc(hidden)] macro_rules! py_binary_num_func { - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => {{ - #[allow(unused_mut)] + ($trait:ident, $class:ident :: $f:ident, $conv:expr) => {{ unsafe extern "C" fn wrap( lhs: *mut ffi::PyObject, rhs: *mut ffi::PyObject, @@ -130,11 +127,11 @@ macro_rules! py_binary_num_func { }}; } +// NOTE(kngwyu): This macro is used only for inplace operations, so I used call_mut here. #[macro_export] #[doc(hidden)] macro_rules! py_binary_self_func { ($trait:ident, $class:ident :: $f:ident) => {{ - #[allow(unused_mut)] unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, arg: *mut ffi::PyObject, @@ -146,22 +143,15 @@ macro_rules! py_binary_self_func { let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf1 = py.mut_from_borrowed_ptr::(slf); + let slf_ = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg = py.from_borrowed_ptr::<$crate::types::PyAny>(arg); - - let result = match arg.extract() { - Ok(arg) => slf1.$f(arg).into(), - Err(e) => Err(e.into()), - }; + let result = call_mut!(slf_, $f, arg); match result { Ok(_) => { ffi::Py_INCREF(slf); slf } - Err(e) => { - e.restore(py); - std::ptr::null_mut() - } + Err(e) => e.restore_and_null(py), } } Some(wrap::<$class>) @@ -171,8 +161,16 @@ macro_rules! py_binary_self_func { #[macro_export] #[doc(hidden)] macro_rules! py_ssizearg_func { - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => {{ - #[allow(unused_mut)] + // Use call_ref! by default + ($trait:ident, $class:ident :: $f:ident, $conv:expr) => { + py_ssizearg_func!( + $trait, + $class::$f, + $conv, + call_ref_with_converter + ) + }; + ($trait:ident, $class:ident :: $f:ident, $conv:expr, $call:ident) => {{ unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, arg: $crate::ffi::Py_ssize_t, @@ -182,9 +180,8 @@ macro_rules! py_ssizearg_func { { let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); - let result = slf.$f(arg.into()).into(); - $crate::callback::cb_convert($conv, py, result) + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); + $call!(slf, $conv, py, $f ;arg.into()) } Some(wrap::<$class>) }}; @@ -193,16 +190,10 @@ macro_rules! py_ssizearg_func { #[macro_export] #[doc(hidden)] macro_rules! py_ternary_func { - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => { - py_ternary_func!( - $trait, - $class::$f, - $res_type, - $conv, - *mut $crate::ffi::PyObject - ); + ($trait:ident, $class:ident :: $f:ident, $conv:expr) => { + py_ternary_func!($trait, $class::$f, $conv, *mut $crate::ffi::PyObject); }; - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr, $return_type:ty) => {{ + ($trait:ident, $class:ident :: $f:ident, $conv:expr, $return_type:ty) => {{ unsafe extern "C" fn wrap( slf: *mut $crate::ffi::PyObject, arg1: *mut $crate::ffi::PyObject, @@ -215,18 +206,11 @@ macro_rules! py_ternary_func { let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg1 = py.from_borrowed_ptr::<$crate::types::PyAny>(arg1); let arg2 = py.from_borrowed_ptr::<$crate::types::PyAny>(arg2); - let result = match arg1.extract() { - Ok(arg1) => match arg2.extract() { - Ok(arg2) => slf.$f(arg1, arg2).into(), - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - }; - $crate::callback::cb_convert($conv, py, result) + call_ref_with_converter!(slf, $conv, py, $f, arg1, arg2) } Some(wrap::) @@ -236,7 +220,7 @@ macro_rules! py_ternary_func { #[macro_export] #[doc(hidden)] macro_rules! py_ternary_num_func { - ($trait:ident, $class:ident :: $f:ident, $res_type:ty, $conv:expr) => {{ + ($trait:ident, $class:ident :: $f:ident, $conv:expr) => {{ unsafe extern "C" fn wrap( arg1: *mut $crate::ffi::PyObject, arg2: *mut $crate::ffi::PyObject, @@ -286,31 +270,19 @@ macro_rules! py_ternary_self_func { let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf1 = py.mut_from_borrowed_ptr::(slf); + let slf_cell = py.from_borrowed_ptr::<$crate::PyCell>(slf); let arg1 = py.from_borrowed_ptr::<$crate::types::PyAny>(arg1); let arg2 = py.from_borrowed_ptr::<$crate::types::PyAny>(arg2); - - let result = match arg1.extract() { - Ok(arg1) => match arg2.extract() { - Ok(arg2) => slf1.$f(arg1, arg2).into(), - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - }; - + let result = call_mut!(slf_cell, $f, arg1, arg2); match result { Ok(_) => slf, - Err(e) => { - e.restore(py); - std::ptr::null_mut() - } + Err(e) => e.restore_and_null(py), } } Some(wrap::) }}; } -#[doc(hidden)] macro_rules! py_func_set { ($trait_name:ident, $generic:ident, $fn_set:ident) => {{ unsafe extern "C" fn wrap<$generic>( @@ -325,7 +297,7 @@ macro_rules! py_func_set { let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::<$generic>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); let result = if value.is_null() { Err($crate::PyErr::new::( @@ -335,22 +307,13 @@ macro_rules! py_func_set { ), )) } else { - let name = py.mut_from_borrowed_ptr::<$crate::types::PyAny>(name); + let name = py.from_borrowed_ptr::<$crate::types::PyAny>(name); let value = py.from_borrowed_ptr::<$crate::types::PyAny>(value); - match name.extract() { - Ok(name) => match value.extract() { - Ok(value) => slf.$fn_set(name, value).into(), - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - } + call_mut!(slf, $fn_set, name, value) }; match result { Ok(_) => 0, - Err(e) => { - e.restore(py); - -1 - } + Err(e) => e.restore_and_minus1(py), } } @@ -358,7 +321,6 @@ macro_rules! py_func_set { }}; } -#[doc(hidden)] macro_rules! py_func_del { ($trait_name:ident, $generic:ident, $fn_del:ident) => {{ unsafe extern "C" fn wrap( @@ -375,13 +337,10 @@ macro_rules! py_func_del { let _pool = $crate::GILPool::new(py); let result = if value.is_null() { - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let name = py.from_borrowed_ptr::<$crate::types::PyAny>(name); - match name.extract() { - Ok(name) => slf.$fn_del(name).into(), - Err(e) => Err(e.into()), - } + call_mut!(slf, $fn_del, name) } else { Err(PyErr::new::( "Subscript assignment not supported", @@ -389,10 +348,7 @@ macro_rules! py_func_del { }; match result { Ok(_) => 0, - Err(e) => { - e.restore(py); - -1 - } + Err(e) => e.restore_and_minus1(py), } } @@ -400,7 +356,6 @@ macro_rules! py_func_del { }}; } -#[doc(hidden)] macro_rules! py_func_set_del { ($trait1:ident, $trait2:ident, $generic:ident, $fn_set:ident, $fn_del:ident) => {{ unsafe extern "C" fn wrap<$generic>( @@ -415,32 +370,70 @@ macro_rules! py_func_set_del { let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::<$generic>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); let name = py.from_borrowed_ptr::<$crate::types::PyAny>(name); let result = if value.is_null() { - match name.extract() { - Ok(name) => slf.$fn_del(name).into(), - Err(e) => Err(e.into()), - } + call_mut!(slf, $fn_del, name) } else { let value = py.from_borrowed_ptr::<$crate::types::PyAny>(value); - match name.extract() { - Ok(name) => match value.extract() { - Ok(value) => slf.$fn_set(name, value).into(), - Err(e) => Err(e.into()), - }, - Err(e) => Err(e.into()), - } + call_mut!(slf, $fn_set, name, value) }; match result { Ok(_) => 0, - Err(e) => { - e.restore(py); - -1 - } + Err(e) => e.restore_and_minus1(py), } } Some(wrap::<$generic>) }}; } + +macro_rules! _call_impl { + ($slf: ident, $fn: ident $(; $args: expr)*) => { $slf.$fn($($args,)*).into() }; + ($slf: ident, $fn: ident, $raw_arg: expr $(,$raw_args: expr)* $(; $args: expr)*) => { + match $raw_arg.extract() { + Ok(arg) => _call_impl!($slf, $fn $(,$raw_args)* $(;$args)* ;arg), + Err(e) => Err(e.into()), + } + }; +} + +/// Call `slf.try_borrow()?.$fn(...)` +macro_rules! call_ref { + ($slf: expr, $fn: ident $(,$raw_args: expr)* $(; $args: expr)*) => { + match $slf.try_borrow() { + Ok(slf) => _call_impl!(slf, $fn $(,$raw_args)* $(;$args)*), + Err(e) => Err(e.into()), + } + }; +} + +/// Call `slf.try_borrow()?.$fn(...)` and returns the result using the given CallbackConverter +macro_rules! call_ref_with_converter { + ($slf: expr, $conv: expr, $py: ident, $fn: ident $(,$raw_args: expr)* $(; $args: expr)*) => { + match $slf.try_borrow() { + Ok(slf) => $crate::callback::cb_convert($conv, $py, _call_impl!(slf, $fn $(,$raw_args)* $(;$args)*)), + Err(e) => $crate::callback::cb_err($conv, $py, e) + } + }; +} + +/// Call `slf.try_borrow_mut()?.$fn(...)` +macro_rules! call_mut { + ($slf: expr, $fn: ident $(,$raw_args: expr)* $(; $args: expr)*) => { + match $slf.try_borrow_mut() { + Ok(mut slf) => _call_impl!(slf, $fn $(,$raw_args)* $(;$args)*), + Err(e) => Err(e.into()), + } + }; +} + +/// Call `slf.try_borrow_mut()?.$fn(...)` and returns the result using the given CallbackConverter +macro_rules! call_mut_with_converter { + ($slf: expr, $conv: expr, $py: ident, $fn: ident $(,$raw_args: expr)* $(; $args: expr)*) => { + match $slf.try_borrow_mut() { + Ok(mut slf) => $crate::callback::cb_convert($conv, $py, _call_impl!(slf, $fn $(,$raw_args)* $(;$args)*)), + Err(e) => $crate::callback::cb_err($conv, $py, e) + } + }; +} diff --git a/src/class/mapping.rs b/src/class/mapping.rs index f1c4b3ff..39373134 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -6,15 +6,11 @@ use crate::callback::{LenResultConverter, PyObjectCallbackConverter}; use crate::class::methods::PyMethodDef; use crate::err::{PyErr, PyResult}; -use crate::ffi; -use crate::type_object::PyTypeInfo; -use crate::FromPyObject; -use crate::Python; -use crate::{exceptions, IntoPy, PyObject}; +use crate::{exceptions, ffi, FromPyObject, IntoPy, PyClass, PyObject, Python}; /// Mapping interface #[allow(unused_variables)] -pub trait PyMappingProtocol<'p>: PyTypeInfo { +pub trait PyMappingProtocol<'p>: PyClass { fn __len__(&'p self) -> Self::Result where Self: PyMappingLenProtocol<'p>, @@ -171,8 +167,7 @@ where py_binary_func!( PyMappingGetItemProtocol, T::__getitem__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } diff --git a/src/class/number.rs b/src/class/number.rs index 796b8bad..1be54f54 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -7,13 +7,11 @@ use crate::callback::PyObjectCallbackConverter; use crate::class::basic::PyObjectProtocolImpl; use crate::class::methods::PyMethodDef; use crate::err::PyResult; -use crate::type_object::PyTypeInfo; -use crate::FromPyObject; -use crate::{ffi, IntoPy, PyObject}; +use crate::{ffi, FromPyObject, IntoPy, PyClass, PyObject}; /// Number interface #[allow(unused_variables)] -pub trait PyNumberProtocol<'p>: PyTypeInfo { +pub trait PyNumberProtocol<'p>: PyClass { fn __add__(lhs: Self::Left, rhs: Self::Right) -> Self::Result where Self: PyNumberAddProtocol<'p>, @@ -766,8 +764,7 @@ where py_binary_num_func!( PyNumberAddProtocol, T::__add__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -793,8 +790,7 @@ where py_binary_num_func!( PyNumberSubProtocol, T::__sub__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -820,8 +816,7 @@ where py_binary_num_func!( PyNumberMulProtocol, T::__mul__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -847,8 +842,7 @@ where py_binary_num_func!( PyNumberMatmulProtocol, T::__matmul__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -874,8 +868,7 @@ where py_binary_num_func!( PyNumberTruedivProtocol, T::__truediv__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -901,8 +894,7 @@ where py_binary_num_func!( PyNumberFloordivProtocol, T::__floordiv__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -928,8 +920,7 @@ where py_binary_num_func!( PyNumberModProtocol, T::__mod__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -955,8 +946,7 @@ where py_binary_num_func!( PyNumberDivmodProtocol, T::__divmod__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -982,8 +972,7 @@ where py_ternary_num_func!( PyNumberPowProtocol, T::__pow__, - ::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1009,8 +998,7 @@ where py_binary_num_func!( PyNumberLShiftProtocol, T::__lshift__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1036,8 +1024,7 @@ where py_binary_num_func!( PyNumberRShiftProtocol, T::__rshift__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1063,8 +1050,7 @@ where py_binary_num_func!( PyNumberAndProtocol, T::__and__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1090,8 +1076,7 @@ where py_binary_num_func!( PyNumberXorProtocol, T::__xor__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1117,8 +1102,7 @@ where py_binary_num_func!( PyNumberOrProtocol, T::__or__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1622,8 +1606,7 @@ where py_unary_func!( PyNumberNegProtocol, T::__neg__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1649,8 +1632,7 @@ where py_unary_func!( PyNumberPosProtocol, T::__pos__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1676,8 +1658,7 @@ where py_unary_func!( PyNumberAbsProtocol, T::__abs__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1703,8 +1684,7 @@ where py_unary_func!( PyNumberInvertProtocol, T::__invert__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1730,8 +1710,7 @@ where py_unary_func!( PyNumberIntProtocol, T::__int__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1757,8 +1736,7 @@ where py_unary_func!( PyNumberFloatProtocol, T::__float__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -1784,8 +1762,7 @@ where py_unary_func!( PyNumberIndexProtocol, T::__index__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } diff --git a/src/class/pyasync.rs b/src/class/pyasync.rs index 2536d4ce..842219ca 100644 --- a/src/class/pyasync.rs +++ b/src/class/pyasync.rs @@ -11,15 +11,13 @@ use crate::callback::PyObjectCallbackConverter; use crate::class::methods::PyMethodDef; use crate::err::PyResult; -use crate::ffi; -use crate::type_object::PyTypeInfo; -use crate::PyObject; +use crate::{ffi, PyClass, PyObject}; /// Python Async/Await support interface. /// /// Each method in this trait corresponds to Python async/await implementation. #[allow(unused_variables)] -pub trait PyAsyncProtocol<'p>: PyTypeInfo { +pub trait PyAsyncProtocol<'p>: PyClass { fn __await__(&'p self) -> Self::Result where Self: PyAsyncAwaitProtocol<'p>, @@ -155,8 +153,7 @@ where py_unary_func!( PyAsyncAwaitProtocol, T::__await__, - ::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -183,8 +180,7 @@ where py_unary_func!( PyAsyncAiterProtocol, T::__aiter__, - ::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -208,17 +204,20 @@ mod anext { use crate::IntoPyPointer; use crate::Python; use crate::{ffi, IntoPy, PyObject}; + use std::marker::PhantomData; use std::ptr; - pub struct IterANextResultConverter; + struct IterANextResultConverter(PhantomData); - impl CallbackConverter> for IterANextResultConverter + impl CallbackConverter for IterANextResultConverter where T: IntoPy, { - type R = *mut ffi::PyObject; + type Source = Option; + type Result = *mut ffi::PyObject; + const ERR_VALUE: Self::Result = ptr::null_mut(); - fn convert(val: Option, py: Python) -> *mut ffi::PyObject { + fn convert(val: Self::Source, py: Python) -> Self::Result { match val { Some(val) => val.into_py(py).into_ptr(), None => unsafe { @@ -227,11 +226,6 @@ mod anext { }, } } - - #[inline] - fn error_value() -> *mut ffi::PyObject { - ptr::null_mut() - } } impl PyAsyncAnextProtocolImpl for T @@ -243,8 +237,9 @@ mod anext { py_unary_func!( PyAsyncAnextProtocol, T::__anext__, - Option, - IterANextResultConverter + IterANextResultConverter::(std::marker::PhantomData), + *mut crate::ffi::PyObject, + call_mut_with_converter ) } } diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 5414704a..b753a059 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -5,18 +5,14 @@ use crate::callback::{BoolCallbackConverter, LenResultConverter, PyObjectCallbackConverter}; use crate::err::{PyErr, PyResult}; -use crate::ffi; use crate::objectprotocol::ObjectProtocol; -use crate::type_object::PyTypeInfo; use crate::types::PyAny; -use crate::FromPyObject; -use crate::Python; -use crate::{exceptions, IntoPy, PyObject}; +use crate::{exceptions, ffi, FromPyObject, IntoPy, PyClass, PyObject, Python}; use std::os::raw::c_int; /// Sequence interface #[allow(unused_variables)] -pub trait PySequenceProtocol<'p>: PyTypeInfo + Sized { +pub trait PySequenceProtocol<'p>: PyClass + Sized { fn __len__(&'p self) -> Self::Result where Self: PySequenceLenProtocol<'p>, @@ -206,8 +202,7 @@ where py_ssizearg_func!( PySequenceGetItemProtocol, T::__getitem__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -267,27 +262,29 @@ mod sq_ass_item_impl { { let py = Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::>(slf); - let result = if value.is_null() { - Err(PyErr::new::(format!( + if value.is_null() { + return PyErr::new::(format!( "Item deletion is not supported by {:?}", stringify!(T) - ))) - } else { - let value = py.from_borrowed_ptr::(value); - match value.extract() { - Ok(value) => slf.__setitem__(key.into(), value).into(), - Err(e) => Err(e), - } - }; + )) + .restore_and_minus1(py); + } + let result = match slf.try_borrow_mut() { + Ok(mut slf) => { + let value = py.from_borrowed_ptr::(value); + match value.extract() { + Ok(value) => slf.__setitem__(key.into(), value).into(), + Err(e) => e.into(), + } + } + Err(e) => Err(PyErr::from(e)), + }; match result { Ok(_) => 0, - Err(e) => { - e.restore(py); - -1 - } + Err(e) => e.restore_and_minus1(py), } } Some(wrap::) @@ -322,10 +319,10 @@ mod sq_ass_item_impl { { let py = Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::>(slf); let result = if value.is_null() { - slf.__delitem__(key.into()).into() + slf.borrow_mut().__delitem__(key.into()).into() } else { Err(PyErr::new::(format!( "Item assignment not supported by {:?}", @@ -335,10 +332,7 @@ mod sq_ass_item_impl { match result { Ok(_) => 0, - Err(e) => { - e.restore(py); - -1 - } + Err(e) => e.restore_and_minus1(py), } } Some(wrap::) @@ -373,23 +367,23 @@ mod sq_ass_item_impl { { let py = Python::assume_gil_acquired(); let _pool = crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); + let slf = py.from_borrowed_ptr::>(slf); let result = if value.is_null() { - slf.__delitem__(key.into()).into() + call_mut!(slf, __delitem__; key.into()) } else { let value = py.from_borrowed_ptr::(value); - match value.extract() { - Ok(value) => slf.__setitem__(key.into(), value).into(), - Err(e) => Err(e), + match slf.try_borrow_mut() { + Ok(mut slf_) => match value.extract() { + Ok(value) => slf_.__setitem__(key.into(), value).into(), + Err(e) => Err(e), + }, + Err(e) => Err(e.into()), } }; match result { Ok(_) => 0, - Err(e) => { - e.restore(py); - -1 - } + Err(e) => e.restore_and_minus1(py), } } Some(wrap::) @@ -418,7 +412,6 @@ where py_binary_func!( PySequenceContainsProtocol, T::__contains__, - bool, BoolCallbackConverter, c_int ) @@ -446,8 +439,7 @@ where py_binary_func!( PySequenceConcatProtocol, T::__concat__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -473,8 +465,7 @@ where py_ssizearg_func!( PySequenceRepeatProtocol, T::__repeat__, - T::Success, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData) ) } } @@ -500,8 +491,9 @@ where py_binary_func!( PySequenceInplaceConcatProtocol, T::__inplace_concat__, - T, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData), + *mut crate::ffi::PyObject, + call_mut_with_converter ) } } @@ -527,8 +519,8 @@ where py_ssizearg_func!( PySequenceInplaceRepeatProtocol, T::__inplace_repeat__, - T, - PyObjectCallbackConverter + PyObjectCallbackConverter::(std::marker::PhantomData), + call_mut_with_converter ) } } diff --git a/src/conversion.rs b/src/conversion.rs index 7a9259ec..9c2e802d 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -3,10 +3,10 @@ //! Conversions between various states of rust and python types and their wrappers. use crate::err::{self, PyDowncastError, PyResult}; use crate::object::PyObject; -use crate::type_object::{PyObjectLayout, PyTypeInfo}; +use crate::type_object::{PyDowncastImpl, PyTypeInfo}; use crate::types::PyAny; use crate::types::PyTuple; -use crate::{ffi, gil, Py, Python}; +use crate::{ffi, gil, Py, PyCell, PyClass, PyNativeType, PyRef, PyRefMut, Python}; use std::ptr::NonNull; /// This trait represents that, **we can do zero-cost conversion from the object to FFI pointer**. @@ -244,80 +244,42 @@ where } } -#[doc(hidden)] -pub mod extract_impl { - use super::*; - - pub trait ExtractImpl<'a, Target>: Sized { - fn extract(source: &'a PyAny) -> PyResult; +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) } - - pub struct Cloned; - pub struct Reference; - pub struct MutReference; - - impl<'a, T: 'a> ExtractImpl<'a, T> for Cloned - where - T: Clone, - Reference: ExtractImpl<'a, &'a T>, - { - fn extract(source: &'a PyAny) -> PyResult { - Ok(Reference::extract(source)?.clone()) - } - } - - impl<'a, T> ExtractImpl<'a, &'a T> for Reference - where - T: PyTryFrom<'a>, - { - fn extract(source: &'a PyAny) -> PyResult<&'a T> { - Ok(T::try_from(source)?) - } - } - - impl<'a, T> ExtractImpl<'a, &'a mut T> for MutReference - where - T: PyTryFrom<'a>, - { - fn extract(source: &'a PyAny) -> PyResult<&'a mut T> { - Ok(T::try_from_mut(source)?) - } - } -} - -use extract_impl::ExtractImpl; - -/// This is an internal trait for re-using `FromPyObject` implementations for many pyo3 types. -/// -/// Users should implement `FromPyObject` directly instead of via this trait. -pub trait FromPyObjectImpl { - // Implement this trait with to specify the implementor of `extract_impl::ExtractImpl` to use for - // extracting this type from Python objects. - // - // Example valid implementations are `extract_impl::Cloned`, `extract_impl::Reference`, and - // `extract_impl::MutReference`, which are for extracting `T`, `&T` and `&mut T` respectively via - // PyTryFrom. - // - // We deliberately don't require Impl: ExtractImpl here because we allow #[pyclass] - // to specify an Impl which doesn't satisfy the ExtractImpl constraints. - // - // e.g. non-clone #[pyclass] can still have Impl: Cloned. - // - // We catch invalid Impls in the blanket impl for FromPyObject, which only - // complains when .extract() is actually used. - - /// The type which implements `ExtractImpl`. - type Impl; } impl<'a, T> FromPyObject<'a> for T where - T: FromPyObjectImpl, - ::Impl: ExtractImpl<'a, Self>, + T: PyClass + Clone, { - #[inline] - fn extract(ob: &'a PyAny) -> PyResult { - ::Impl::extract(ob) + 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) } } @@ -337,6 +299,22 @@ where } } +/// 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 + PyDowncastImpl { + /// Cast from a concrete Python object type to PyObject. + fn try_from>(value: V) -> Result<&'v Self, PyDowncastError>; + + /// Cast from a concrete Python object type to PyObject. With exact type check. + fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError>; + + /// Cast a PyAny to a specific type of PyObject. The caller must + /// have already verified the reference is for this type. + 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 { @@ -345,37 +323,6 @@ pub trait PyTryInto: Sized { /// Cast from PyObject to a concrete Python object type. With exact type check. fn try_into_exact(&self) -> Result<&T, PyDowncastError>; - - /// Cast from PyObject to a concrete Python object type. - fn try_into_mut(&self) -> Result<&mut T, PyDowncastError>; - - /// Cast from PyObject to a concrete Python object type. With exact type check. - fn try_into_mut_exact(&self) -> Result<&mut T, PyDowncastError>; -} - -/// Trait implemented by Python object types that allow a checked downcast. -/// This trait is similar to `std::convert::TryFrom` -pub trait PyTryFrom<'v>: Sized { - /// Cast from a concrete Python object type to PyObject. - fn try_from>(value: V) -> Result<&'v Self, PyDowncastError>; - - /// Cast from a concrete Python object type to PyObject. With exact type check. - fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError>; - - /// Cast from a concrete Python object type to PyObject. - fn try_from_mut>(value: V) -> Result<&'v mut Self, PyDowncastError>; - - /// Cast from a concrete Python object type to PyObject. With exact type check. - fn try_from_mut_exact>(value: V) -> Result<&'v mut Self, PyDowncastError>; - - /// Cast a PyAny to a specific type of PyObject. The caller must - /// have already verified the reference is for this type. - unsafe fn try_from_unchecked>(value: V) -> &'v Self; - - /// Cast a PyAny to a specific type of PyObject. The caller must - /// have already verified the reference is for this type. - #[allow(clippy::mut_from_ref)] - unsafe fn try_from_mut_unchecked>(value: V) -> &'v mut Self; } // TryFrom implies TryInto @@ -389,56 +336,28 @@ where fn try_into_exact(&self) -> Result<&U, PyDowncastError> { U::try_from_exact(self) } - fn try_into_mut(&self) -> Result<&mut U, PyDowncastError> { - U::try_from_mut(self) - } - fn try_into_mut_exact(&self) -> Result<&mut U, PyDowncastError> { - U::try_from_mut_exact(self) - } } impl<'v, T> PyTryFrom<'v> for T where - T: PyTypeInfo, + T: PyDowncastImpl + PyTypeInfo + PyNativeType, { - fn try_from>(value: V) -> Result<&'v T, PyDowncastError> { + fn try_from>(value: V) -> Result<&'v Self, PyDowncastError> { let value = value.into(); unsafe { if T::is_instance(value) { - Ok(PyTryFrom::try_from_unchecked(value)) + Ok(Self::try_from_unchecked(value)) } else { Err(PyDowncastError) } } } - fn try_from_exact>(value: V) -> Result<&'v T, PyDowncastError> { + fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError> { let value = value.into(); unsafe { if T::is_exact_instance(value) { - Ok(PyTryFrom::try_from_unchecked(value)) - } else { - Err(PyDowncastError) - } - } - } - - fn try_from_mut>(value: V) -> Result<&'v mut T, PyDowncastError> { - let value = value.into(); - unsafe { - if T::is_instance(value) { - Ok(PyTryFrom::try_from_mut_unchecked(value)) - } else { - Err(PyDowncastError) - } - } - } - - fn try_from_mut_exact>(value: V) -> Result<&'v mut T, PyDowncastError> { - let value = value.into(); - unsafe { - if T::is_exact_instance(value) { - Ok(PyTryFrom::try_from_mut_unchecked(value)) + Ok(Self::try_from_unchecked(value)) } else { Err(PyDowncastError) } @@ -446,15 +365,38 @@ where } #[inline] - unsafe fn try_from_unchecked>(value: V) -> &'v T { - let value = value.into(); - T::ConcreteLayout::internal_ref_cast(value) + unsafe fn try_from_unchecked>(value: V) -> &'v Self { + Self::unchecked_downcast(value.into()) } +} - #[inline] - unsafe fn try_from_mut_unchecked>(value: V) -> &'v mut T { +impl<'v, T> PyTryFrom<'v> for PyCell +where + T: 'v + PyClass, +{ + fn try_from>(value: V) -> Result<&'v Self, PyDowncastError> { let value = value.into(); - T::ConcreteLayout::internal_mut_cast(value) + unsafe { + if T::is_instance(value) { + Ok(Self::try_from_unchecked(value)) + } else { + Err(PyDowncastError) + } + } + } + fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError> { + let value = value.into(); + unsafe { + if T::is_exact_instance(value) { + Ok(Self::try_from_unchecked(value)) + } else { + Err(PyDowncastError) + } + } + } + #[inline] + unsafe fn try_from_unchecked>(value: V) -> &'v Self { + Self::unchecked_downcast(value.into()) } } @@ -467,33 +409,37 @@ impl FromPy<()> for Py { /// Raw level conversion between `*mut ffi::PyObject` and PyO3 types. pub unsafe trait FromPyPointer<'p>: Sized { - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option; - unsafe fn from_owned_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> Self { + unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self>; + unsafe fn from_owned_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { match Self::from_owned_ptr_or_opt(py, ptr) { Some(s) => s, None => err::panic_after_error(), } } - unsafe fn from_owned_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> Self { + unsafe fn from_owned_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { Self::from_owned_ptr_or_panic(py, ptr) } - unsafe fn from_owned_ptr_or_err(py: Python<'p>, ptr: *mut ffi::PyObject) -> PyResult { + unsafe fn from_owned_ptr_or_err(py: Python<'p>, ptr: *mut ffi::PyObject) -> PyResult<&'p Self> { match Self::from_owned_ptr_or_opt(py, ptr) { Some(s) => Ok(s), None => Err(err::PyErr::fetch(py)), } } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option; - unsafe fn from_borrowed_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> Self { + unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) + -> Option<&'p Self>; + unsafe fn from_borrowed_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { match Self::from_borrowed_ptr_or_opt(py, ptr) { Some(s) => s, None => err::panic_after_error(), } } - unsafe fn from_borrowed_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> Self { + unsafe fn from_borrowed_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { Self::from_borrowed_ptr_or_panic(py, ptr) } - unsafe fn from_borrowed_ptr_or_err(py: Python<'p>, ptr: *mut ffi::PyObject) -> PyResult { + unsafe fn from_borrowed_ptr_or_err( + py: Python<'p>, + ptr: *mut ffi::PyObject, + ) -> PyResult<&'p Self> { match Self::from_borrowed_ptr_or_opt(py, ptr) { Some(s) => Ok(s), None => Err(err::PyErr::fetch(py)), @@ -501,29 +447,33 @@ pub unsafe trait FromPyPointer<'p>: Sized { } } -unsafe impl<'p, T> FromPyPointer<'p> for &'p T +unsafe impl<'p, T> FromPyPointer<'p> for T where - T: PyTypeInfo, + T: 'p + crate::PyNativeType, { - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| T::ConcreteLayout::internal_ref_cast(gil::register_owned(py, p))) + unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self> { + NonNull::new(ptr).map(|p| Self::unchecked_downcast(gil::register_owned(py, p))) } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr) - .map(|p| T::ConcreteLayout::internal_ref_cast(gil::register_borrowed(py, p))) + unsafe fn from_borrowed_ptr_or_opt( + py: Python<'p>, + ptr: *mut ffi::PyObject, + ) -> Option<&'p Self> { + NonNull::new(ptr).map(|p| Self::unchecked_downcast(gil::register_borrowed(py, p))) } } -unsafe impl<'p, T> FromPyPointer<'p> for &'p mut T +unsafe impl<'p, T> FromPyPointer<'p> for PyCell where - T: PyTypeInfo, + T: PyClass, { - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| T::ConcreteLayout::internal_mut_cast(gil::register_owned(py, p))) + unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self> { + NonNull::new(ptr).map(|p| &*(gil::register_owned(py, p).as_ptr() as *const PyCell)) } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr) - .map(|p| T::ConcreteLayout::internal_mut_cast(gil::register_borrowed(py, p))) + unsafe fn from_borrowed_ptr_or_opt( + py: Python<'p>, + ptr: *mut ffi::PyObject, + ) -> Option<&'p Self> { + NonNull::new(ptr).map(|p| &*(gil::register_borrowed(py, p).as_ptr() as *const PyCell)) } } diff --git a/src/derive_utils.rs b/src/derive_utils.rs index 62e3e2ef..7b666754 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -182,8 +182,8 @@ impl> IntoPyResult for PyResult { } } -/// Variant of IntoPyResult for the specific case of #[new]. In the case of returning (Sub, Base) -/// from #[new], IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy. +/// Variant of IntoPyResult for the specific case of `#[new]`. In the case of returning (Sub, Base) +/// from `#[new]`, IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy. pub trait IntoPyNewResult>> { fn into_pynew_result(self) -> PyResult; } @@ -218,3 +218,18 @@ impl GetPropertyValue for PyObject { self.clone_ref(py) } } + +/// Utilities for basetype +pub trait PyBaseTypeUtils { + type Dict; + type WeakRef; + type LayoutAsBase; + type BaseNativeType; +} + +impl PyBaseTypeUtils for T { + type Dict = T::Dict; + type WeakRef = T::WeakRef; + type LayoutAsBase = crate::pycell::PyCellInner; + type BaseNativeType = T::BaseNativeType; +} diff --git a/src/err.rs b/src/err.rs index 337d9a1e..9076529a 100644 --- a/src/err.rs +++ b/src/err.rs @@ -331,13 +331,20 @@ impl PyErr { unsafe { ffi::PyErr_Restore(ptype.into_ptr(), pvalue, ptraceback.into_ptr()) } } - #[doc(hidden)] /// Utility method for proc-macro code + #[doc(hidden)] pub fn restore_and_null(self, py: Python) -> *mut T { self.restore(py); std::ptr::null_mut() } + /// Utility method for proc-macro code + #[doc(hidden)] + pub fn restore_and_minus1(self, py: Python) -> crate::libc::c_int { + self.restore(py); + -1 + } + /// Issue a warning message. /// May return a PyErr if warnings-as-errors is enabled. pub fn warn(py: Python, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> { @@ -401,7 +408,7 @@ impl<'a> IntoPy for &'a PyErr { } } -/// Converts `PyDowncastError` to Python `TypeError`. +/// Convert `PyDowncastError` to Python `TypeError`. impl std::convert::From for PyErr { fn from(_err: PyDowncastError) -> PyErr { exceptions::TypeError.into() diff --git a/src/ffi/setobject.rs b/src/ffi/setobject.rs index 9f628280..6f9c832b 100644 --- a/src/ffi/setobject.rs +++ b/src/ffi/setobject.rs @@ -21,6 +21,7 @@ pub struct setentry { } #[repr(C)] +#[derive(Debug)] pub struct PySetObject { pub ob_base: PyObject, pub fill: Py_ssize_t, diff --git a/src/freelist.rs b/src/freelist.rs index 9dd44edf..d3a4ae4e 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -4,7 +4,7 @@ use crate::ffi; use crate::pyclass::{tp_free_fallback, PyClassAlloc}; -use crate::type_object::{PyObjectLayout, PyTypeInfo}; +use crate::type_object::{PyLayout, PyTypeInfo}; use crate::Python; use std::mem; use std::os::raw::c_void; @@ -72,7 +72,7 @@ impl PyClassAlloc for T where T: PyTypeInfo + PyClassWithFreeList, { - unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { + unsafe fn alloc(_py: Python) -> *mut Self::Layout { if let Some(obj) = ::get_free_list().pop() { ffi::PyObject_Init(obj, ::type_object() as *const _ as _); obj as _ @@ -81,7 +81,7 @@ where } } - unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { + unsafe fn dealloc(py: Python, self_: *mut Self::Layout) { (*self_).py_drop(py); let obj = self_ as _; diff --git a/src/instance.rs b/src/instance.rs index 34f9e7df..3c7db293 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -3,12 +3,12 @@ use crate::err::{PyErr, PyResult}; use crate::gil; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; -use crate::pyclass::{PyClass, PyClassShell}; -use crate::pyclass_init::PyClassInitializer; -use crate::type_object::{PyObjectLayout, PyTypeInfo}; +use crate::type_object::{PyBorrowFlagLayout, PyDowncastImpl}; use crate::types::PyAny; -use crate::{ffi, IntoPy}; -use crate::{AsPyPointer, FromPyObject, IntoPyPointer, Python, ToPyObject}; +use crate::{ + ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyCell, PyClass, PyClassInitializer, + PyRef, PyRefMut, Python, ToPyObject, +}; use std::marker::PhantomData; use std::mem; use std::ptr::NonNull; @@ -35,15 +35,18 @@ unsafe impl Send for Py {} unsafe impl Sync for Py {} impl Py { - /// Create new instance of T and move it under python management + /// Create a new instance `Py`. + /// + /// This method is **soft-duplicated** since PyO3 0.9.0. + /// Use [`PyCell::new`](../pycell/struct.PyCell.html#method.new) and + /// `Py::from` instead. pub fn new(py: Python, value: impl Into>) -> PyResult> where T: PyClass, - ::ConcreteLayout: - crate::type_object::PyObjectSizedLayout, + T::BaseLayout: PyBorrowFlagLayout, { let initializer = value.into(); - let obj = unsafe { initializer.create_shell(py)? }; + let obj = unsafe { initializer.create_cell(py)? }; let ob = unsafe { Py::from_owned_ptr(obj as _) }; Ok(ob) } @@ -119,15 +122,47 @@ impl Py { } } -pub trait AsPyRef: Sized { +/// Retrives `&'py` types from `Py` or `PyObject`. +/// +/// # Examples +/// `PyObject::as_ref` returns `&PyAny`. +/// ``` +/// # use pyo3::prelude::*; +/// use pyo3::ObjectProtocol; +/// let obj: PyObject = { +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// py.eval("[]", None, None).unwrap().to_object(py) +/// }; +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// assert_eq!(obj.as_ref(py).len().unwrap(), 0); // PyAny implements ObjectProtocol +/// ``` +/// `Py::as_ref` returns `&PyDict`, `&PyList` or so for native types, and `&PyCell` +/// for `#[pyclass]`. +/// ``` +/// # use pyo3::prelude::*; +/// use pyo3::ObjectProtocol; +/// let obj: PyObject = { +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// py.eval("[]", None, None).unwrap().to_object(py) +/// }; +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// assert_eq!(obj.as_ref(py).len().unwrap(), 0); // PyAny implements ObjectProtocol +/// ``` +pub trait AsPyRef: Sized { + type Target; /// Return reference to object. - fn as_ref(&self, py: Python) -> &T; + fn as_ref(&self, py: Python<'_>) -> &Self::Target; } -impl AsPyRef for Py { - fn as_ref(&self, _py: Python) -> &T { +impl<'p, T: PyClass> AsPyRef for Py { + type Target = PyCell; + fn as_ref(&self, _py: Python) -> &PyCell { let any = self as *const Py as *const PyAny; - unsafe { T::ConcreteLayout::internal_ref_cast(&*any) } + unsafe { PyDowncastImpl::unchecked_downcast(&*any) } } } @@ -174,22 +209,31 @@ where } } -// `&PyClassShell` can be converted to `Py` -impl<'a, T> std::convert::From<&PyClassShell> for Py +// `&PyCell` can be converted to `Py` +impl<'a, T> std::convert::From<&PyCell> for Py where T: PyClass, { - fn from(shell: &PyClassShell) -> Self { - unsafe { Py::from_borrowed_ptr(shell.as_ptr()) } + fn from(cell: &PyCell) -> Self { + unsafe { Py::from_borrowed_ptr(cell.as_ptr()) } } } -impl<'a, T> std::convert::From<&mut PyClassShell> for Py +impl<'a, T> std::convert::From> for Py where T: PyClass, { - fn from(shell: &mut PyClassShell) -> Self { - unsafe { Py::from_borrowed_ptr(shell.as_ptr()) } + fn from(pyref: PyRef<'a, T>) -> Self { + unsafe { Py::from_borrowed_ptr(pyref.as_ptr()) } + } +} + +impl<'a, T> std::convert::From> for Py +where + T: PyClass, +{ + fn from(pyref: PyRefMut<'a, T>) -> Self { + unsafe { Py::from_borrowed_ptr(pyref.as_ptr()) } } } diff --git a/src/internal_tricks.rs b/src/internal_tricks.rs index 1d6a5eb9..0dae9fb8 100644 --- a/src/internal_tricks.rs +++ b/src/internal_tricks.rs @@ -23,3 +23,10 @@ macro_rules! private_impl { } } } + +macro_rules! pyo3_exception { + ($name: ident, $base: ty) => { + $crate::impl_exception_boilerplate!($name); + $crate::create_exception_type_object!(pyo3_runtime, $name, $base); + }; +} diff --git a/src/lib.rs b/src/lib.rs index c7e06b7c..6f1f342e 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,7 +128,8 @@ pub use crate::gil::{init_once, GILGuard, GILPool}; pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; -pub use crate::pyclass::{PyClass, PyClassShell}; +pub use crate::pycell::{PyCell, PyRef, PyRefMut}; +pub use crate::pyclass::PyClass; pub use crate::pyclass_init::PyClassInitializer; pub use crate::python::{prepare_freethreaded_python, Python}; pub use crate::type_object::{type_flags, PyTypeInfo}; @@ -171,6 +172,7 @@ pub mod marshal; mod object; mod objectprotocol; pub mod prelude; +pub mod pycell; pub mod pyclass; pub mod pyclass_init; pub mod pyclass_slots; @@ -222,7 +224,7 @@ macro_rules! wrap_pymodule { /// /// # Example /// ``` -/// use pyo3::{prelude::*, py_run, PyClassShell}; +/// use pyo3::{prelude::*, py_run, PyCell}; /// #[pyclass] /// #[derive(Debug)] /// struct Time { @@ -245,7 +247,7 @@ macro_rules! wrap_pymodule { /// } /// let gil = Python::acquire_gil(); /// let py = gil.python(); -/// let time = PyClassShell::new_ref(py, Time {hour: 8, minute: 43, second: 16}).unwrap(); +/// let time = PyCell::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap(); /// let time_as_tuple = (8, 43, 16); /// py_run!(py, time time_as_tuple, r#" /// assert time.hour == 8 diff --git a/src/object.rs b/src/object.rs index c465a8c8..83c726ee 100644 --- a/src/object.rs +++ b/src/object.rs @@ -149,16 +149,16 @@ impl PyObject { } /// Casts the PyObject to a concrete Python object type. - pub fn cast_as(&self, py: Python) -> Result<&D, PyDowncastError> + pub fn cast_as<'p, D>(&'p self, py: Python<'p>) -> Result<&'p D, PyDowncastError> where - D: for<'v> PyTryFrom<'v>, + D: PyTryFrom<'p>, { D::try_from(self.as_ref(py)) } /// Extracts some type from the Python object. /// This is a wrapper function around `FromPyObject::extract()`. - pub fn extract<'p, D>(&'p self, py: Python) -> PyResult + pub fn extract<'p, D>(&'p self, py: Python<'p>) -> PyResult where D: FromPyObject<'p>, { @@ -250,7 +250,8 @@ impl PyObject { } } -impl AsPyRef for PyObject { +impl AsPyRef for PyObject { + type Target = PyAny; fn as_ref(&self, _py: Python) -> &PyAny { unsafe { &*(self as *const _ as *const PyAny) } } diff --git a/src/objectprotocol.rs b/src/objectprotocol.rs index 93451a44..a2822a27 100644 --- a/src/objectprotocol.rs +++ b/src/objectprotocol.rs @@ -3,16 +3,11 @@ use crate::class::basic::CompareOp; use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::exceptions::TypeError; -use crate::ffi; -use crate::instance::PyNativeType; -use crate::object::PyObject; -use crate::type_object::PyTypeInfo; use crate::types::{PyAny, PyDict, PyIterator, PyString, PyTuple, PyType}; -use crate::AsPyPointer; -use crate::IntoPyPointer; -use crate::Py; -use crate::Python; -use crate::{FromPyObject, IntoPy, PyTryFrom, ToBorrowedObject, ToPyObject}; +use crate::{ + ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyNativeType, PyObject, PyTryFrom, + Python, ToBorrowedObject, ToPyObject, +}; use std::cmp::Ordering; use std::os::raw::c_int; @@ -175,17 +170,6 @@ pub trait ObjectProtocol { /// Gets the Python type pointer for this object. fn get_type_ptr(&self) -> *mut ffi::PyTypeObject; - /// Gets the Python base object for this object. - fn get_base(&self) -> &::BaseType - where - Self: PyTypeInfo; - - /// Gets the Python base object for this object. - - fn get_mut_base(&mut self) -> &mut ::BaseType - where - Self: PyTypeInfo; - /// Casts the PyObject to a concrete Python object type. fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError> where @@ -455,20 +439,6 @@ where unsafe { (*self.as_ptr()).ob_type } } - fn get_base(&self) -> &::BaseType - where - Self: PyTypeInfo, - { - unsafe { self.py().from_borrowed_ptr(self.as_ptr()) } - } - - fn get_mut_base(&mut self) -> &mut ::BaseType - where - Self: PyTypeInfo, - { - unsafe { self.py().mut_from_borrowed_ptr(self.as_ptr()) } - } - fn cast_as<'a, D>(&'a self) -> Result<&'a D, PyDowncastError> where D: PyTryFrom<'a>, diff --git a/src/prelude.rs b/src/prelude.rs index 4e98df7c..d97879a5 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,6 +15,7 @@ pub use crate::gil::GILGuard; pub use crate::instance::{AsPyRef, Py}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; +pub use crate::pycell::{PyCell, PyRef, PyRefMut}; pub use crate::pyclass_init::PyClassInitializer; pub use crate::python::Python; pub use crate::{FromPy, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, ToPyObject}; diff --git a/src/pycell.rs b/src/pycell.rs new file mode 100644 index 00000000..2a5500c8 --- /dev/null +++ b/src/pycell.rs @@ -0,0 +1,678 @@ +//! Includes `PyCell` implementation. +use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject}; +use crate::pyclass_init::PyClassInitializer; +use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; +use crate::type_object::{PyBorrowFlagLayout, PyDowncastImpl, PyLayout, PySizedLayout, PyTypeInfo}; +use crate::types::PyAny; +use crate::{ffi, FromPy, PyClass, PyErr, PyNativeType, PyObject, PyResult, Python}; +use std::cell::{Cell, UnsafeCell}; +use std::fmt; +use std::mem::ManuallyDrop; +use std::ops::{Deref, DerefMut}; + +/// Base layout of PyCell. +/// This is necessary for sharing BorrowFlag between parents and children. +#[doc(hidden)] +#[repr(C)] +pub struct PyCellBase { + ob_base: T::Layout, + borrow_flag: Cell, +} + +unsafe impl PyLayout for PyCellBase +where + T: PyTypeInfo + PyNativeType, + T::Layout: PySizedLayout, +{ + const IS_NATIVE_TYPE: bool = true; + unsafe fn get_ptr(&self) -> *mut T { + (&self) as *const &Self as *const _ as *mut _ + } +} + +// Thes impls ensures `PyCellBase` can be a base type. +impl PySizedLayout for PyCellBase +where + T: PyTypeInfo + PyNativeType, + T::Layout: PySizedLayout, +{ +} + +unsafe impl PyBorrowFlagLayout for PyCellBase +where + T: PyTypeInfo + PyNativeType, + T::Layout: PySizedLayout, +{ +} + +/// Inner type of `PyCell` without dict slots and reference counter. +/// This struct has two usages: +/// 1. As an inner type of `PyRef` and `PyRefMut`. +/// 2. When `#[pyclass(extends=Base)]` is specified, `PyCellInner` is used as a base layout. +#[doc(hidden)] +#[repr(C)] +pub struct PyCellInner { + ob_base: T::BaseLayout, + value: ManuallyDrop>, +} + +impl AsPyPointer for PyCellInner { + fn as_ptr(&self) -> *mut ffi::PyObject { + (self as *const _) as *mut _ + } +} + +unsafe impl PyLayout for PyCellInner { + const IS_NATIVE_TYPE: bool = false; + fn get_super(&mut self) -> Option<&mut T::BaseLayout> { + Some(&mut self.ob_base) + } + unsafe fn get_ptr(&self) -> *mut T { + self.value.get() + } + unsafe fn py_init(&mut self, value: T) { + self.value = ManuallyDrop::new(UnsafeCell::new(value)); + } + unsafe fn py_drop(&mut self, py: Python) { + ManuallyDrop::drop(&mut self.value); + self.ob_base.py_drop(py); + } +} + +// These impls ensures `PyCellInner` can be a base type. +impl PySizedLayout for PyCellInner {} +unsafe impl PyBorrowFlagLayout for PyCellInner {} + +impl PyCellInner { + fn get_borrow_flag(&self) -> BorrowFlag { + let base = (&self.ob_base) as *const _ as *const PyCellBase; + unsafe { (*base).borrow_flag.get() } + } + fn set_borrow_flag(&self, flag: BorrowFlag) { + let base = (&self.ob_base) as *const _ as *const PyCellBase; + unsafe { (*base).borrow_flag.set(flag) } + } +} + +/// `PyCell` is the container type for [`PyClass`](../pyclass/trait.PyClass.html). +/// +/// From Python side, `PyCell` is the concrete layout of `T: PyClass` in the Python heap, +/// which means we can convert `*const PyClass` to `*mut ffi::PyObject`. +/// +/// From Rust side, `PyCell` is the mutable container of `T`. +/// Since `PyCell` is always on the Python heap, we don't have the ownership of it. +/// Thus, to mutate the data behind `&PyCell` safely, we employ the +/// [Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html) +/// like [std::cell::RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html). +/// +/// # Examples +/// +/// In most cases, `PyCell` is hidden behind `#[pymethods]`. +/// However, you can construct `&PyCell` directly to test your pyclass in Rust code. +/// +/// ``` +/// # use pyo3::prelude::*; +/// #[pyclass] +/// struct Book { +/// #[pyo3(get)] +/// name: &'static str, +/// author: &'static str, +/// } +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// let book = Book { +/// name: "The Man in the High Castle", +/// author: "Philip Kindred Dick", +/// }; +/// let book_cell = PyCell::new(py, book).unwrap(); +/// // you can expose PyCell to Python snippets +/// pyo3::py_run!(py, book_cell, "assert book_cell.name[-6:] == 'Castle'"); +/// ``` +/// You can use `slf: &PyCell` as an alternative `self` receiver of `#[pymethod]`, +/// though you rarely need it. +/// ``` +/// # use pyo3::prelude::*; +/// use std::collections::HashMap; +/// #[pyclass] +/// #[derive(Default)] +/// struct Counter { +/// counter: HashMap +/// } +/// #[pymethods] +/// impl Counter { +/// // You can use &mut self here, but now we use &PyCell for demonstration +/// fn increment(slf: &PyCell, name: String) -> PyResult { +/// let mut slf_mut = slf.try_borrow_mut()?; +/// // Now a mutable reference exists so we cannot get another one +/// assert!(slf.try_borrow().is_err()); +/// assert!(slf.try_borrow_mut().is_err()); +/// let counter = slf_mut.counter.entry(name).or_insert(0); +/// *counter += 1; +/// Ok(*counter) +/// } +/// } +/// # let gil = Python::acquire_gil(); +/// # let py = gil.python(); +/// # let counter = PyCell::new(py, Counter::default()).unwrap(); +/// # pyo3::py_run!(py, counter, "assert counter.increment('cat') == 1"); +/// ``` +#[repr(C)] +pub struct PyCell { + inner: PyCellInner, + dict: T::Dict, + weakref: T::WeakRef, +} + +impl PyCell { + /// Make new `PyCell` on the Python heap and returns the reference of it. + /// + pub fn new(py: Python, value: impl Into>) -> PyResult<&Self> + where + T::BaseLayout: PyBorrowFlagLayout, + { + unsafe { + let initializer = value.into(); + let self_ = initializer.create_cell(py)?; + FromPyPointer::from_owned_ptr_or_err(py, self_ as _) + } + } + + /// Immutably borrows the value `T`. This borrow lasts untill the returned `PyRef` exists. + /// + /// # Panics + /// + /// Panics if the value is currently mutably borrowed. For a non-panicking variant, use + /// [`try_borrow`](#method.try_borrow). + pub fn borrow(&self) -> PyRef<'_, T> { + self.try_borrow().expect("Already mutably borrowed") + } + + /// Mutably borrows the value `T`. This borrow lasts untill the returned `PyRefMut` exists. + /// + /// # 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(&self) -> PyRefMut<'_, T> { + self.try_borrow_mut().expect("Already borrowed") + } + + /// Immutably borrows the value `T`, returning an error if the value is currently + /// mutably borrowed. This borrow lasts untill the returned `PyRef` exists. + /// + /// This is the non-panicking variant of [`borrow`](#method.borrow). + /// + /// # Examples + /// + /// ``` + /// # use pyo3::prelude::*; + /// #[pyclass] + /// struct Class {} + /// let gil = Python::acquire_gil(); + /// let py = gil.python(); + /// let c = PyCell::new(py, Class {}).unwrap(); + /// { + /// let m = c.borrow_mut(); + /// assert!(c.try_borrow().is_err()); + /// } + /// + /// { + /// let m = c.borrow(); + /// assert!(c.try_borrow().is_ok()); + /// } + /// ``` + pub fn try_borrow(&self) -> Result, PyBorrowError> { + let flag = self.inner.get_borrow_flag(); + if flag == BorrowFlag::HAS_MUTABLE_BORROW { + Err(PyBorrowError { _private: () }) + } else { + self.inner.set_borrow_flag(flag.increment()); + Ok(PyRef { inner: &self.inner }) + } + } + + /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. + /// This borrow lasts untill the returned `PyRefMut` exists. + /// + /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). + /// + /// # Examples + /// + /// ``` + /// # use pyo3::prelude::*; + /// #[pyclass] + /// struct Class {} + /// let gil = Python::acquire_gil(); + /// let py = gil.python(); + /// 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.inner.get_borrow_flag() != BorrowFlag::UNUSED { + Err(PyBorrowMutError { _private: () }) + } else { + self.inner.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); + Ok(PyRefMut { inner: &self.inner }) + } + } + + /// Immutably borrows the value `T`, returning an error if the value is + /// currently mutably borrowed. + /// + /// # Safety + /// + /// 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] + /// struct Class {} + /// let gil = Python::acquire_gil(); + /// let py = gil.python(); + /// 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()); + /// } + /// ``` + pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { + if self.inner.get_borrow_flag() == BorrowFlag::HAS_MUTABLE_BORROW { + Err(PyBorrowError { _private: () }) + } else { + Ok(&*self.inner.value.get()) + } + } + + /// Replaces the wrapped value with a new one, returning the old value, + /// + /// # Panics + /// + /// Panics if the value is currently borrowed. + #[inline] + pub fn replace(&self, t: T) -> T { + std::mem::replace(&mut *self.borrow_mut(), t) + } + + /// Replaces the wrapped value with a new one computed from `f`, returning the old value. + /// + /// # Panics + /// + /// Panics if the value is currently borrowed. + pub fn replace_with T>(&self, f: F) -> T { + let mut_borrow = &mut *self.borrow_mut(); + let replacement = f(mut_borrow); + std::mem::replace(mut_borrow, replacement) + } + + /// Swaps the wrapped value of `self` with the wrapped value of `other`. + /// + /// # Panics + /// + /// Panics if the value in either `PyCell` is currently borrowed. + #[inline] + pub fn swap(&self, other: &Self) { + std::mem::swap(&mut *self.borrow_mut(), &mut *other.borrow_mut()) + } + + /// Allocates new PyCell without initilizing value. + /// Requires `T::BaseLayout: PyBorrowFlagLayout` to ensure `self` has a borrow flag. + pub(crate) unsafe fn internal_new(py: Python) -> PyResult<*mut Self> + where + T::BaseLayout: PyBorrowFlagLayout, + { + let base = T::alloc(py); + if base.is_null() { + return Err(PyErr::fetch(py)); + } + let base = base as *mut PyCellBase; + (*base).borrow_flag = Cell::new(BorrowFlag::UNUSED); + let self_ = base as *mut Self; + (*self_).dict = T::Dict::new(); + (*self_).weakref = T::WeakRef::new(); + Ok(self_) + } +} + +unsafe impl PyLayout for PyCell { + const IS_NATIVE_TYPE: bool = false; + fn get_super(&mut self) -> Option<&mut T::BaseLayout> { + Some(&mut self.inner.ob_base) + } + unsafe fn get_ptr(&self) -> *mut T { + self.inner.get_ptr() + } + unsafe fn py_init(&mut self, value: T) { + self.inner.value = ManuallyDrop::new(UnsafeCell::new(value)); + } + unsafe fn py_drop(&mut self, py: Python) { + ManuallyDrop::drop(&mut self.inner.value); + self.dict.clear_dict(py); + self.weakref.clear_weakrefs(self.as_ptr(), py); + self.inner.ob_base.py_drop(py); + } +} + +unsafe impl PyDowncastImpl for PyCell { + unsafe fn unchecked_downcast(obj: &PyAny) -> &Self { + &*(obj.as_ptr() as *const Self) + } + private_impl! {} +} + +impl AsPyPointer for PyCell { + fn as_ptr(&self) -> *mut ffi::PyObject { + self.inner.as_ptr() + } +} + +impl ToPyObject for &PyCell { + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } + } +} + +impl fmt::Debug for PyCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.try_borrow() { + Ok(borrow) => f.debug_struct("RefCell").field("value", &borrow).finish(), + Err(_) => { + struct BorrowedPlaceholder; + impl fmt::Debug for BorrowedPlaceholder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("") + } + } + f.debug_struct("RefCell") + .field("value", &BorrowedPlaceholder) + .finish() + } + } + } +} + +/// Wraps a borrowed reference to a value in a `PyCell`. +/// +/// See the [`PyCell`](struct.PyCell.html) documentation for more. +/// # Example +/// You can use `PyRef` as an alternative of `&self` receiver when +/// - You need to access the pointer of `PyCell`. +/// - You want to get super class. +/// ``` +/// # use pyo3::prelude::*; +/// #[pyclass] +/// struct Parent { +/// basename: &'static str, +/// } +/// #[pyclass(extends=Parent)] +/// struct Child { +/// name: &'static str, +/// } +/// #[pymethods] +/// impl Child { +/// #[new] +/// fn new() -> (Self, Parent) { +/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" }) +/// } +/// fn format(slf: PyRef) -> String { +/// // We can get *mut ffi::PyObject from PyRef +/// use pyo3::AsPyPointer; +/// let refcnt = unsafe { pyo3::ffi::Py_REFCNT(slf.as_ptr()) }; +/// // We can get &Self::BaseType by as_ref +/// let basename = slf.as_ref().basename; +/// format!("{}(base: {}, cnt: {})", slf.name, basename, refcnt) +/// } +/// } +/// # let gil = Python::acquire_gil(); +/// # let py = gil.python(); +/// # let sub = PyCell::new(py, Child::new()).unwrap(); +/// # pyo3::py_run!(py, sub, "assert sub.format() == 'Caterpillar(base: Butterfly, cnt: 4)'"); +/// ``` +pub struct PyRef<'p, T: PyClass> { + inner: &'p PyCellInner, +} + +impl<'p, T: PyClass> AsRef for PyRef<'p, T> { + fn as_ref(&self) -> &T::BaseType { + unsafe { &*self.inner.ob_base.get_ptr() } + } +} + +impl<'p, T, U> PyRef<'p, T> +where + T: PyClass + PyTypeInfo>, + U: PyClass, +{ + /// Get `PyRef`. + /// You can use this method to get super class of super class. + /// + /// # Examples + /// ``` + /// # use pyo3::prelude::*; + /// #[pyclass] + /// struct Base1 { + /// name1: &'static str, + /// } + /// #[pyclass(extends=Base1)] + /// struct Base2 { + /// name2: &'static str, + /// } + /// #[pyclass(extends=Base2)] + /// struct Sub { + /// name3: &'static str, + /// } + /// #[pymethods] + /// impl Sub { + /// #[new] + /// fn new() -> PyClassInitializer { + /// PyClassInitializer::from(Base1{ name1: "base1" }) + /// .add_subclass(Base2 { name2: "base2" }) + /// .add_subclass(Self { name3: "sub" }) + /// } + /// fn name(slf: PyRef) -> String { + /// let subname = slf.name3; + /// let super_ = slf.into_super(); + /// format!("{} {} {}", super_.as_ref().name1, super_.name2, subname) + /// } + /// } + /// # let gil = Python::acquire_gil(); + /// # let py = gil.python(); + /// # let sub = PyCell::new(py, Sub::new()).unwrap(); + /// # pyo3::py_run!(py, sub, "assert sub.name() == 'base1 base2 sub'") + /// ``` + pub fn into_super(self) -> PyRef<'p, U> { + let PyRef { inner } = self; + std::mem::forget(self); + PyRef { + inner: &inner.ob_base, + } + } +} + +impl<'p, T: PyClass> Deref for PyRef<'p, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + unsafe { &*self.inner.get_ptr() } + } +} + +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()) + } +} + +impl<'p, T: PyClass> FromPy> for PyObject { + fn from_py(pyref: PyRef<'p, T>, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(py, pyref.inner.as_ptr()) } + } +} + +impl<'a, T: PyClass> std::convert::TryFrom<&'a PyCell> for crate::PyRef<'a, T> { + type Error = PyBorrowError; + fn try_from(cell: &'a crate::PyCell) -> Result { + cell.try_borrow() + } +} + +impl<'a, T: PyClass> AsPyPointer for PyRef<'a, T> { + fn as_ptr(&self) -> *mut ffi::PyObject { + self.inner.as_ptr() + } +} + +impl fmt::Debug for PyRef<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +/// Wraps a mutable borrowed reference to a value in a `PyCell`. +/// +/// See the [`PyCell`](struct.PyCell.html) and [`PyRef`](struct.PyRef.html) documentations for more. +pub struct PyRefMut<'p, T: PyClass> { + inner: &'p PyCellInner, +} + +impl<'p, T: PyClass> AsRef for PyRefMut<'p, T> { + fn as_ref(&self) -> &T::BaseType { + unsafe { &*self.inner.ob_base.get_ptr() } + } +} + +impl<'p, T: PyClass> AsMut for PyRefMut<'p, T> { + fn as_mut(&mut self) -> &mut T::BaseType { + unsafe { &mut *self.inner.ob_base.get_ptr() } + } +} + +impl<'p, T, U> PyRefMut<'p, T> +where + T: PyClass + PyTypeInfo>, + U: PyClass, +{ + /// Get `PyRef`. + /// See [`PyRef::into_super`](struct.PyRef.html#method.into_super) for more. + pub fn into_super(self) -> PyRefMut<'p, U> { + let PyRefMut { inner } = self; + std::mem::forget(self); + PyRefMut { + inner: &inner.ob_base, + } + } +} + +impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + unsafe { &*self.inner.get_ptr() } + } +} + +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: PyClass> Drop for PyRefMut<'p, T> { + fn drop(&mut self) { + self.inner.set_borrow_flag(BorrowFlag::UNUSED) + } +} + +impl<'p, T: PyClass> FromPy> for PyObject { + fn from_py(pyref: PyRefMut<'p, T>, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(py, pyref.inner.as_ptr()) } + } +} + +impl<'a, T: PyClass> 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> { + type Error = PyBorrowMutError; + fn try_from(cell: &'a crate::PyCell) -> Result { + cell.try_borrow_mut() + } +} + +impl fmt::Debug for PyRefMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&*(self.deref()), f) + } +} + +#[derive(Copy, Clone, Eq, PartialEq)] +struct BorrowFlag(usize); + +impl BorrowFlag { + const UNUSED: BorrowFlag = BorrowFlag(0); + const HAS_MUTABLE_BORROW: BorrowFlag = BorrowFlag(usize::max_value()); + const fn increment(self) -> Self { + Self(self.0 + 1) + } + const fn decrement(self) -> Self { + Self(self.0 - 1) + } +} + +/// An error returned by [`PyCell::try_borrow`](struct.PyCell.html#method.try_borrow). +pub struct PyBorrowError { + _private: (), +} + +impl fmt::Debug for PyBorrowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PyBorrowError").finish() + } +} + +impl fmt::Display for PyBorrowError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt("Already mutably borrowed", f) + } +} + +/// An error returned by [`PyCell::try_borrow_mut`](struct.PyCell.html#method.try_borrow_mut). +pub struct PyBorrowMutError { + _private: (), +} + +impl fmt::Debug for PyBorrowMutError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PyBorrowMutError").finish() + } +} + +impl fmt::Display for PyBorrowMutError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt("Already borrowed", f) + } +} + +pyo3_exception!(PyBorrowError, crate::exceptions::Exception); +pyo3_exception!(PyBorrowMutError, crate::exceptions::Exception); diff --git a/src/pyclass.rs b/src/pyclass.rs index 12fb801b..f9d1441c 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,22 +1,17 @@ -//! Traits and structs for `#[pyclass]`. +//! `PyClass` trait use crate::class::methods::{PyMethodDefType, PyMethodsProtocol}; -use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject}; -use crate::pyclass_init::PyClassInitializer; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; -use crate::type_object::{type_flags, PyObjectLayout, PyObjectSizedLayout}; -use crate::types::PyAny; -use crate::{class, ffi, gil, PyErr, PyObject, PyResult, PyTypeInfo, Python}; +use crate::type_object::{type_flags, PyLayout}; +use crate::{class, ffi, gil, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python}; use std::ffi::CString; -use std::mem::ManuallyDrop; use std::os::raw::c_void; -use std::ptr::{self, NonNull}; +use std::ptr; #[inline] pub(crate) unsafe fn default_alloc() -> *mut ffi::PyObject { let type_obj = T::type_object(); - if T::FLAGS & type_flags::EXTENDED != 0 - && ::ConcreteLayout::IS_NATIVE_TYPE - { + // if the class derives native types(e.g., PyDict), call special new + if T::FLAGS & type_flags::EXTENDED != 0 && T::BaseLayout::IS_NATIVE_TYPE { let base_tp = ::type_object(); if let Some(base_new) = base_tp.tp_new { return base_new(type_obj as *const _ as _, ptr::null_mut(), ptr::null_mut()); @@ -32,7 +27,7 @@ pub trait PyClassAlloc: PyTypeInfo + Sized { /// /// # Safety /// This function must return a valid pointer to the Python heap. - unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { + unsafe fn alloc(_py: Python) -> *mut Self::Layout { default_alloc::() as _ } @@ -40,7 +35,7 @@ pub trait PyClassAlloc: PyTypeInfo + Sized { /// /// # Safety /// `self_` must be a valid pointer to the Python heap. - unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { + unsafe fn dealloc(py: Python, self_: *mut Self::Layout) { (*self_).py_drop(py); let obj = self_ as _; if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 { @@ -71,185 +66,20 @@ pub unsafe fn tp_free_fallback(obj: *mut ffi::PyObject) { } /// If `PyClass` is implemented for `T`, then we can use `T` in the Python world, -/// via `PyClassShell`. +/// via `PyCell`. /// /// The `#[pyclass]` attribute automatically implements this trait for your Rust struct, /// so you don't have to use this trait directly. pub trait PyClass: - PyTypeInfo> + Sized + PyClassAlloc + PyMethodsProtocol + PyTypeInfo> + Sized + PyClassAlloc + PyMethodsProtocol { + /// Specify this class has `#[pyclass(dict)]` or not. type Dict: PyClassDict; + /// Specify this class has `#[pyclass(weakref)]` or not. type WeakRef: PyClassWeakRef; -} - -/// `PyClassShell` represents the concrete layout of `T: PyClass` when it is converted -/// to a Python class. -/// -/// You can use it to test your `#[pyclass]` correctly works. -/// -/// ``` -/// # use pyo3::prelude::*; -/// # use pyo3::{py_run, PyClassShell}; -/// #[pyclass] -/// struct Book { -/// #[pyo3(get)] -/// name: &'static str, -/// author: &'static str, -/// } -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let book = Book { -/// name: "The Man in the High Castle", -/// author: "Philip Kindred Dick", -/// }; -/// let book_shell = PyClassShell::new_ref(py, book).unwrap(); -/// py_run!(py, book_shell, "assert book_shell.name[-6:] == 'Castle'"); -/// ``` -#[repr(C)] -pub struct PyClassShell { - ob_base: ::ConcreteLayout, - pyclass: ManuallyDrop, - dict: T::Dict, - weakref: T::WeakRef, -} - -impl PyClassShell { - /// Make new `PyClassShell` on the Python heap and returns the reference of it. - pub fn new_ref(py: Python, value: impl Into>) -> PyResult<&Self> - where - ::ConcreteLayout: - crate::type_object::PyObjectSizedLayout, - { - unsafe { - let initializer = value.into(); - let self_ = initializer.create_shell(py)?; - FromPyPointer::from_owned_ptr_or_err(py, self_ as _) - } - } - - /// Make new `PyClassShell` on the Python heap and returns the mutable reference of it. - pub fn new_mut(py: Python, value: impl Into>) -> PyResult<&mut Self> - where - ::ConcreteLayout: - crate::type_object::PyObjectSizedLayout, - { - unsafe { - let initializer = value.into(); - let self_ = initializer.create_shell(py)?; - FromPyPointer::from_owned_ptr_or_err(py, self_ as _) - } - } - - /// Get the reference of base object. - pub fn get_super(&self) -> &::ConcreteLayout { - &self.ob_base - } - - /// Get the mutable reference of base object. - pub fn get_super_mut(&mut self) -> &mut ::ConcreteLayout { - &mut self.ob_base - } - - pub(crate) unsafe fn new(py: Python) -> PyResult<*mut Self> - where - ::ConcreteLayout: - crate::type_object::PyObjectSizedLayout, - { - let base = T::alloc(py); - if base.is_null() { - return Err(PyErr::fetch(py)); - } - let self_ = base as *mut Self; - (*self_).dict = T::Dict::new(); - (*self_).weakref = T::WeakRef::new(); - Ok(self_) - } -} - -impl PyObjectLayout for PyClassShell { - const IS_NATIVE_TYPE: bool = false; - fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { - Some(&mut self.ob_base) - } - unsafe fn internal_ref_cast(obj: &PyAny) -> &T { - let shell = obj.as_ptr() as *const Self; - &(*shell).pyclass - } - unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { - let shell = obj.as_ptr() as *const _ as *mut Self; - &mut (*shell).pyclass - } - unsafe fn py_drop(&mut self, py: Python) { - ManuallyDrop::drop(&mut self.pyclass); - self.dict.clear_dict(py); - self.weakref.clear_weakrefs(self.as_ptr(), py); - self.ob_base.py_drop(py); - } - unsafe fn py_init(&mut self, value: T) { - self.pyclass = ManuallyDrop::new(value); - } -} - -impl PyObjectSizedLayout for PyClassShell {} - -impl AsPyPointer for PyClassShell { - fn as_ptr(&self) -> *mut ffi::PyObject { - (self as *const _) as *mut _ - } -} - -impl std::ops::Deref for PyClassShell { - type Target = T; - fn deref(&self) -> &T { - self.pyclass.deref() - } -} - -impl std::ops::DerefMut for PyClassShell { - fn deref_mut(&mut self) -> &mut T { - self.pyclass.deref_mut() - } -} - -impl ToPyObject for &PyClassShell { - fn to_object(&self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -impl ToPyObject for &mut PyClassShell { - fn to_object(&self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for &'p PyClassShell -where - T: PyClass, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| &*(gil::register_owned(py, p).as_ptr() as *const PyClassShell)) - } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr) - .map(|p| &*(gil::register_borrowed(py, p).as_ptr() as *const PyClassShell)) - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for &'p mut PyClassShell -where - T: PyClass, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| { - &mut *(gil::register_owned(py, p).as_ptr() as *const _ as *mut PyClassShell) - }) - } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| { - &mut *(gil::register_borrowed(py, p).as_ptr() as *const _ as *mut PyClassShell) - }) - } + /// The closest native ancestor. This is `PyAny` by default, and when you declare + /// `#[pyclass(extends=PyDict)]`, it's `PyDict`. + type BaseNativeType: PyTypeInfo + PyNativeType; } #[cfg(not(Py_LIMITED_API))] @@ -285,12 +115,12 @@ where { let py = Python::assume_gil_acquired(); let _pool = gil::GILPool::new_no_pointers(py); - ::dealloc(py, (obj as *mut T::ConcreteLayout) as _) + ::dealloc(py, (obj as *mut T::Layout) as _) } type_object.tp_dealloc = Some(tp_dealloc_callback::); // type size - type_object.tp_basicsize = std::mem::size_of::() as ffi::Py_ssize_t; + type_object.tp_basicsize = std::mem::size_of::() as ffi::Py_ssize_t; let mut offset = type_object.tp_basicsize; diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index bc2904da..3be018f2 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -1,7 +1,6 @@ //! Initialization utilities for `#[pyclass]`. -use crate::pyclass::{PyClass, PyClassShell}; -use crate::type_object::{PyObjectLayout, PyObjectSizedLayout, PyTypeInfo}; -use crate::{PyResult, Python}; +use crate::type_object::{PyBorrowFlagLayout, PyLayout, PySizedLayout, PyTypeInfo}; +use crate::{PyCell, PyClass, PyResult, Python}; use std::marker::PhantomData; /// Initializer for Python types. @@ -9,7 +8,7 @@ use std::marker::PhantomData; /// This trait is intended to use internally for distinguishing `#[pyclass]` and /// Python native types. pub trait PyObjectInit: Sized { - fn init_class(self, shell: &mut T::ConcreteLayout); + fn init_class>(self, layout: &mut L); private_decl! {} } @@ -17,7 +16,7 @@ pub trait PyObjectInit: Sized { pub struct PyNativeTypeInitializer(PhantomData); impl PyObjectInit for PyNativeTypeInitializer { - fn init_class(self, _shell: &mut T::ConcreteLayout) {} + fn init_class>(self, _layout: &mut L) {} private_impl! {} } @@ -109,30 +108,32 @@ impl PyClassInitializer { pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer where S: PyClass + PyTypeInfo, + S::BaseLayout: PySizedLayout, S::BaseType: PyTypeInfo, { PyClassInitializer::new(subclass_value, self) } + // Create a new PyCell + initialize it #[doc(hidden)] - pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell> + pub unsafe fn create_cell(self, py: Python) -> PyResult<*mut PyCell> where T: PyClass, - ::ConcreteLayout: PyObjectSizedLayout, + T::BaseLayout: PyBorrowFlagLayout, { - let shell = PyClassShell::new(py)?; - self.init_class(&mut *shell); - Ok(shell) + let cell = PyCell::internal_new(py)?; + self.init_class(&mut *cell); + Ok(cell) } } impl PyObjectInit for PyClassInitializer { - fn init_class(self, obj: &mut T::ConcreteLayout) { + fn init_class>(self, layout: &mut L) { let Self { init, super_init } = self; unsafe { - obj.py_init(init); + layout.py_init(init); } - if let Some(super_obj) = obj.get_super_or() { + if let Some(super_obj) = layout.get_super() { super_init.init_class(super_obj); } } @@ -152,6 +153,7 @@ where impl From<(S, B)> for PyClassInitializer where S: PyClass + PyTypeInfo, + S::BaseLayout: PySizedLayout, B: PyClass + PyTypeInfo>, B::BaseType: PyTypeInfo>, { diff --git a/src/python.rs b/src/python.rs index e5b31ae8..2944e648 100644 --- a/src/python.rs +++ b/src/python.rs @@ -7,7 +7,7 @@ use crate::ffi; use crate::gil::{self, GILGuard}; use crate::instance::AsPyRef; use crate::object::PyObject; -use crate::type_object::{PyObjectLayout, PyTypeInfo, PyTypeObject}; +use crate::type_object::{PyDowncastImpl, PyTypeInfo, PyTypeObject}; use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::AsPyPointer; use crate::{FromPyPointer, IntoPyPointer, PyTryFrom}; @@ -177,7 +177,7 @@ impl<'p> Python<'p> { /// Some(locals), /// ).unwrap(); /// let ret = locals.get_item("ret").unwrap(); - /// let b64: &PyBytes = ret.downcast_ref().unwrap(); + /// let b64: &PyBytes = ret.downcast().unwrap(); /// assert_eq!(b64.as_bytes(), b"SGVsbG8gUnVzdCE="); /// ``` pub fn run( @@ -275,19 +275,19 @@ impl<'p> Python<'p> { /// Register object in release pool, and try to downcast to specific type. pub fn checked_cast_as(self, obj: PyObject) -> Result<&'p T, PyDowncastError> where - T: PyTypeInfo, + T: PyTryFrom<'p>, { - let p = unsafe { gil::register_owned(self, obj.into_nonnull()) }; - ::try_from(p) + let obj = unsafe { gil::register_owned(self, obj.into_nonnull()) }; + ::try_from(obj) } /// Register object in release pool, and do unchecked downcast to specific type. pub unsafe fn cast_as(self, obj: PyObject) -> &'p T where - T: PyTypeInfo, + T: PyDowncastImpl + PyTypeInfo, { - let p = gil::register_owned(self, obj.into_nonnull()); - T::ConcreteLayout::internal_ref_cast(p) + let obj = gil::register_owned(self, obj.into_nonnull()); + T::unchecked_downcast(obj) } /// Register `ffi::PyObject` pointer in release pool @@ -304,16 +304,7 @@ impl<'p> Python<'p> { #[allow(clippy::wrong_self_convention)] pub unsafe fn from_owned_ptr(self, ptr: *mut ffi::PyObject) -> &'p T where - T: PyTypeInfo, - { - FromPyPointer::from_owned_ptr(self, ptr) - } - - /// Register `ffi::PyObject` pointer in release pool, - /// Do unchecked downcast to specific type. Returns mutable reference. - pub unsafe fn mut_from_owned_ptr(self, ptr: *mut ffi::PyObject) -> &'p mut T - where - T: PyTypeInfo, + T: FromPyPointer<'p>, { FromPyPointer::from_owned_ptr(self, ptr) } @@ -324,7 +315,7 @@ impl<'p> Python<'p> { #[allow(clippy::wrong_self_convention)] pub unsafe fn from_owned_ptr_or_err(self, ptr: *mut ffi::PyObject) -> PyResult<&'p T> where - T: PyTypeInfo, + T: FromPyPointer<'p>, { FromPyPointer::from_owned_ptr_or_err(self, ptr) } @@ -335,7 +326,7 @@ impl<'p> Python<'p> { #[allow(clippy::wrong_self_convention)] pub unsafe fn from_owned_ptr_or_opt(self, ptr: *mut ffi::PyObject) -> Option<&'p T> where - T: PyTypeInfo, + T: FromPyPointer<'p>, { FromPyPointer::from_owned_ptr_or_opt(self, ptr) } @@ -346,17 +337,7 @@ impl<'p> Python<'p> { #[allow(clippy::wrong_self_convention)] pub unsafe fn from_borrowed_ptr(self, ptr: *mut ffi::PyObject) -> &'p T where - T: PyTypeInfo, - { - FromPyPointer::from_borrowed_ptr(self, ptr) - } - - /// Register borrowed `ffi::PyObject` pointer in release pool. - /// Panics if the pointer is `null`. - /// do unchecked downcast to specific type. - pub unsafe fn mut_from_borrowed_ptr(self, ptr: *mut ffi::PyObject) -> &'p mut T - where - T: PyTypeInfo, + T: FromPyPointer<'p>, { FromPyPointer::from_borrowed_ptr(self, ptr) } @@ -367,7 +348,7 @@ impl<'p> Python<'p> { #[allow(clippy::wrong_self_convention)] pub unsafe fn from_borrowed_ptr_or_err(self, ptr: *mut ffi::PyObject) -> PyResult<&'p T> where - T: PyTypeInfo, + T: FromPyPointer<'p>, { FromPyPointer::from_borrowed_ptr_or_err(self, ptr) } @@ -378,7 +359,7 @@ impl<'p> Python<'p> { #[allow(clippy::wrong_self_convention)] pub unsafe fn from_borrowed_ptr_or_opt(self, ptr: *mut ffi::PyObject) -> Option<&'p T> where - T: PyTypeInfo, + T: FromPyPointer<'p>, { FromPyPointer::from_borrowed_ptr_or_opt(self, ptr) } diff --git a/src/type_object.rs b/src/type_object.rs index 6b12542a..4530b1b0 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -1,46 +1,49 @@ // Copyright (c) 2017-present PyO3 Project and Contributors //! Python type object information -use crate::instance::Py; use crate::pyclass::{initialize_type_object, PyClass}; use crate::pyclass_init::PyObjectInit; use crate::types::{PyAny, PyType}; -use crate::{ffi, AsPyPointer, Python}; +use crate::{ffi, AsPyPointer, Py, Python}; use std::cell::UnsafeCell; use std::ptr::NonNull; use std::sync::atomic::{AtomicBool, Ordering}; -/// `T: PyObjectLayout` represents that `T` is a concrete representaion of `U` in Python heap. -/// E.g., `PyClassShell` is a concrete representaion of all `pyclass`es, and `ffi::PyObject` +/// `T: PyLayout` represents that `T` is a concrete representaion of `U` in Python heap. +/// E.g., `PyCell` is a concrete representaion of all `pyclass`es, and `ffi::PyObject` /// is of `PyAny`. /// /// This trait is intended to be used internally. -pub trait PyObjectLayout { +pub unsafe trait PyLayout { const IS_NATIVE_TYPE: bool = true; - - fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { + fn get_super(&mut self) -> Option<&mut T::BaseLayout> { None } - - unsafe fn internal_ref_cast(obj: &PyAny) -> &T { - &*(obj as *const _ as *const T) - } - - #[allow(clippy::mut_from_ref)] - unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { - &mut *(obj as *const _ as *const T as *mut T) - } - unsafe fn py_init(&mut self, _value: T) {} unsafe fn py_drop(&mut self, _py: Python) {} + unsafe fn get_ptr(&self) -> *mut T; } -/// `T: PyObjectSizedLayout` represents `T` is not a instance of +/// `T: PySizedLayout` represents `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`. +pub trait PySizedLayout: PyLayout + Sized {} + +/// Marker type indicates that `Self` can be a base layout of `PyClass`. /// -/// `pyclass`es need this trait for their base class. -pub trait PyObjectSizedLayout: PyObjectLayout + Sized {} +/// # Safety +/// +/// Self should be laid out as follows: +/// ```ignore +/// #[repr(C)] +/// struct Self { +/// obj: ffi::PyObject, +/// borrow_flag: u64, +/// ... +/// } +/// ``` +/// Otherwise, implementing this trait is undefined behavior. +pub unsafe trait PyBorrowFlagLayout: PyLayout + Sized {} /// Our custom type flags #[doc(hidden)] @@ -61,6 +64,32 @@ pub mod type_flags { pub const EXTENDED: usize = 1 << 4; } +/// Reference abstraction for `PyClass` and `PyNativeType`. Used internaly. +// NOTE(kngwyu): +// `&PyCell` is a pointer of `ffi::PyObject` but `&PyAny` is a pointer of a pointer, +// so we need abstraction. +// This mismatch eventually should be fixed(e.g., https://github.com/PyO3/pyo3/issues/679). +pub unsafe trait PyDowncastImpl { + /// Cast `&PyAny` to `&Self` without no type checking. + /// + /// # Safety + /// + /// Unless obj is not an instance of a type corresponding to Self, + /// this method causes undefined behavior. + unsafe fn unchecked_downcast(obj: &PyAny) -> &Self; + private_decl! {} +} + +unsafe impl<'py, T> PyDowncastImpl for T +where + T: 'py + crate::PyNativeType, +{ + unsafe fn unchecked_downcast(obj: &PyAny) -> &Self { + &*(obj as *const _ as *const Self) + } + private_impl! {} +} + /// Python type information. /// All Python native types(e.g., `PyDict`) and `#[pyclass]` structs implement this trait. /// @@ -87,7 +116,10 @@ pub unsafe trait PyTypeInfo: Sized { type BaseType: PyTypeInfo + PyTypeObject; /// Layout - type ConcreteLayout: PyObjectLayout; + type Layout: PyLayout; + + /// Layout of Basetype. + type BaseLayout: PySizedLayout; /// Initializer for layout type Initializer: PyObjectInit; diff --git a/src/types/any.rs b/src/types/any.rs index a37205f2..76280237 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -18,13 +18,17 @@ use crate::{ffi, PyObject}; /// let dict = PyDict::new(gil.python()); /// assert!(gil.python().is_instance::(dict).unwrap()); /// let any = dict.as_ref(); -/// assert!(any.downcast_ref::().is_ok()); -/// assert!(any.downcast_ref::().is_err()); +/// assert!(any.downcast::().is_ok()); +/// assert!(any.downcast::().is_err()); /// ``` #[repr(transparent)] pub struct PyAny(PyObject, Unsendable); -impl crate::type_object::PyObjectLayout for ffi::PyObject {} -impl crate::type_object::PyObjectSizedLayout for ffi::PyObject {} +unsafe impl crate::type_object::PyLayout for ffi::PyObject { + unsafe fn get_ptr(&self) -> *mut PyAny { + (&self) as *const &Self as *const _ as *mut _ + } +} +impl crate::type_object::PySizedLayout for ffi::PyObject {} pyobject_native_type_named!(PyAny); pyobject_native_type_convert!( PyAny, @@ -36,17 +40,10 @@ pyobject_native_type_convert!( pyobject_native_type_extract!(PyAny); impl PyAny { - pub fn downcast_ref(&self) -> Result<&T, PyDowncastError> + pub fn downcast(&self) -> Result<&T, PyDowncastError> where - T: for<'gil> PyTryFrom<'gil>, + for<'py> T: PyTryFrom<'py>, { - T::try_from(self) - } - - pub fn downcast_mut(&self) -> Result<&mut T, PyDowncastError> - where - T: for<'gil> PyTryFrom<'gil>, - { - T::try_from_mut(self) + ::try_from(self) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index cde4a860..badd4900 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -56,11 +56,27 @@ macro_rules! pyobject_native_type_named ( }; ); +macro_rules! impl_layout { + ($name: ty, $layout: path) => { + unsafe impl $crate::type_object::PyLayout<$name> for $layout { + unsafe fn get_ptr(&self) -> *mut $name { + (&self) as *const &Self as *const _ as *mut _ + } + } + }; +} + #[macro_export] macro_rules! pyobject_native_type { ($name: ty, $layout: path, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { - impl $crate::type_object::PyObjectLayout<$name> for $layout {} - impl $crate::type_object::PyObjectSizedLayout<$name> for $layout {} + impl_layout!($name, $layout); + impl $crate::type_object::PySizedLayout<$name> for $layout {} + impl $crate::derive_utils::PyBaseTypeUtils for $name { + type Dict = $crate::pyclass_slots::PyClassDummySlot; + type WeakRef = $crate::pyclass_slots::PyClassDummySlot; + type LayoutAsBase = $crate::pycell::PyCellBase<$name>; + type BaseNativeType = $name; + } pyobject_native_type_named!($name $(,$type_param)*); pyobject_native_type_convert!($name, $layout, $typeobject, $module, $checkfunction $(,$type_param)*); pyobject_native_type_extract!($name $(,$type_param)*); @@ -81,7 +97,7 @@ macro_rules! pyobject_native_type { #[macro_export] macro_rules! pyobject_native_var_type { ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { - impl $crate::type_object::PyObjectLayout<$name> for $crate::ffi::PyObject {} + impl_layout!($name, $crate::ffi::PyObject); pyobject_native_type_named!($name $(,$type_param)*); pyobject_native_type_convert!($name, $crate::ffi::PyObject, $typeobject, $module, $checkfunction $(,$type_param)*); @@ -104,8 +120,10 @@ macro_rules! pyobject_native_var_type { // because rust-numpy has a special implementation. macro_rules! pyobject_native_type_extract { ($name: ty $(,$type_param: ident)*) => { - impl<$($type_param,)*> $crate::conversion::FromPyObjectImpl for &'_ $name { - type Impl = $crate::conversion::extract_impl::Reference; + impl<'py, $($type_param,)*> $crate::FromPyObject<'py> for &'py $name { + fn extract(obj: &'py crate::types::PyAny) -> $crate::PyResult { + $crate::PyTryFrom::try_from(obj).map_err(Into::into) + } } } } @@ -117,7 +135,8 @@ macro_rules! pyobject_native_type_convert( unsafe impl<$($type_param,)*> $crate::type_object::PyTypeInfo for $name { type Type = (); type BaseType = $crate::types::PyAny; - type ConcreteLayout = $layout; + type Layout = $layout; + type BaseLayout = ffi::PyObject; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; const NAME: &'static str = stringify!($name); @@ -144,6 +163,14 @@ macro_rules! pyobject_native_type_convert( } } + impl $crate::AsPyRef for $crate::Py<$name> { + type Target = $name; + fn as_ref(&self, _py: $crate::Python) -> &$name { + let any = self as *const $crate::Py<$name> as *const $crate::types::PyAny; + unsafe { $crate::type_object::PyDowncastImpl::unchecked_downcast(&*any) } + } + } + impl<$($type_param,)*> ::std::fmt::Debug for $name { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> diff --git a/src/types/module.rs b/src/types/module.rs index d152c529..5f27ea03 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -79,7 +79,7 @@ impl PyModule { /// Return the index (`__all__`) of the module, creating one if needed. pub fn index(&self) -> PyResult<&PyList> { match self.getattr("__all__") { - Ok(idx) => idx.downcast_ref().map_err(PyErr::from), + Ok(idx) => idx.downcast().map_err(PyErr::from), Err(err) => { if err.is_instance::(self.py()) { let l = PyList::empty(self.py()); diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 0de56dfa..4a9cd913 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -298,34 +298,11 @@ impl<'v> PyTryFrom<'v> for PySequence { ::try_from(value) } - fn try_from_mut>(value: V) -> Result<&'v mut PySequence, PyDowncastError> { - let value = value.into(); - unsafe { - if ffi::PySequence_Check(value.as_ptr()) != 0 { - Ok(::try_from_mut_unchecked(value)) - } else { - Err(PyDowncastError) - } - } - } - - fn try_from_mut_exact>( - value: V, - ) -> Result<&'v mut PySequence, PyDowncastError> { - ::try_from_mut(value) - } - #[inline] unsafe fn try_from_unchecked>(value: V) -> &'v PySequence { let ptr = value.into() as *const _ as *const PySequence; &*ptr } - - #[inline] - unsafe fn try_from_mut_unchecked>(value: V) -> &'v mut PySequence { - let ptr = value.into() as *const _ as *mut PySequence; - &mut *ptr - } } #[cfg(test)] diff --git a/src/types/string.rs b/src/types/string.rs index 51d490f1..70b91238 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -3,15 +3,11 @@ use crate::conversion::FromPyObject; use crate::conversion::{PyTryFrom, ToPyObject}; use crate::err::{PyErr, PyResult}; -use crate::gil; use crate::instance::PyNativeType; use crate::internal_tricks::Unsendable; use crate::object::PyObject; use crate::types::PyAny; -use crate::AsPyPointer; -use crate::IntoPy; -use crate::Python; -use crate::{ffi, FromPy}; +use crate::{ffi, gil, AsPyPointer, FromPy, IntoPy, Python}; use std::borrow::Cow; use std::ffi::CStr; use std::os::raw::c_char; diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index 925ad665..c576e1fd 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -4,7 +4,7 @@ use pyo3::exceptions::BufferError; use pyo3::ffi; use pyo3::prelude::*; use pyo3::types::IntoPyDict; -use pyo3::{AsPyPointer, PyClassShell}; +use pyo3::AsPyPointer; use std::ffi::CStr; use std::os::raw::{c_int, c_void}; use std::ptr; @@ -19,11 +19,7 @@ struct TestBufferClass { #[pyproto] impl PyBufferProtocol for TestBufferClass { - fn bf_getbuffer( - slf: &mut PyClassShell, - view: *mut ffi::Py_buffer, - flags: c_int, - ) -> PyResult<()> { + fn bf_getbuffer(slf: PyRefMut, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> { if view.is_null() { return Err(BufferError::py_err("View is null")); } @@ -69,7 +65,7 @@ impl PyBufferProtocol for TestBufferClass { Ok(()) } - fn bf_releasebuffer(_slf: &mut PyClassShell, _view: *mut ffi::Py_buffer) -> PyResult<()> { + fn bf_releasebuffer(_slf: PyRefMut, _view: *mut ffi::Py_buffer) -> PyResult<()> { Ok(()) } } diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index ee3678dd..e04e7375 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -16,10 +16,12 @@ fn test_cloneable_pyclass() { let py_c = Py::new(py, c.clone()).unwrap().to_object(py); let c2: Cloneable = py_c.extract(py).unwrap(); - let rc: &Cloneable = py_c.extract(py).unwrap(); - let mrc: &mut Cloneable = py_c.extract(py).unwrap(); - assert_eq!(c, c2); - assert_eq!(&c, rc); - assert_eq!(&c, mrc); + { + let rc: PyRef = py_c.extract(py).unwrap(); + assert_eq!(&c, &*rc); + // Drops PyRef before taking PyRefMut + } + let mrc: PyRefMut = py_c.extract(py).unwrap(); + assert_eq!(&c, &*mrc); } diff --git a/tests/test_class_new.rs b/tests/test_class_new.rs index ce3107e9..c225608f 100644 --- a/tests/test_class_new.rs +++ b/tests/test_class_new.rs @@ -19,7 +19,7 @@ fn empty_class_with_new() { assert!(typeobj .call((), None) .unwrap() - .cast_as::() + .cast_as::>() .is_ok()); } @@ -43,8 +43,9 @@ fn new_with_one_arg() { let py = gil.python(); let typeobj = py.get_type::(); let wrp = typeobj.call((42,), None).unwrap(); - let obj = wrp.cast_as::().unwrap(); - assert_eq!(obj._data, 42); + let obj = wrp.cast_as::>().unwrap(); + let obj_ref = obj.borrow(); + assert_eq!(obj_ref._data, 42); } #[pyclass] @@ -73,7 +74,8 @@ fn new_with_two_args() { .call((10, 20), None) .map_err(|e| e.print(py)) .unwrap(); - let obj = wrp.cast_as::().unwrap(); - assert_eq!(obj._data1, 10); - assert_eq!(obj._data2, 20); + let obj = wrp.cast_as::>().unwrap(); + let obj_ref = obj.borrow(); + assert_eq!(obj_ref._data1, 10); + assert_eq!(obj_ref._data2, 20); } diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 74183803..5d5d8dfc 100755 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -6,7 +6,7 @@ use pyo3::class::{ use pyo3::exceptions::{IndexError, ValueError}; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyAny, PyBytes, PySlice, PyType}; -use pyo3::{ffi, py_run, AsPyPointer, PyClassShell}; +use pyo3::{ffi, py_run, AsPyPointer, PyCell}; use std::convert::TryFrom; use std::{isize, iter}; @@ -53,11 +53,11 @@ struct Iterator { #[pyproto] impl<'p> PyIterProtocol for Iterator { - fn __iter__(slf: &mut PyClassShell) -> PyResult> { + fn __iter__(slf: PyRefMut) -> PyResult> { Ok(slf.into()) } - fn __next__(slf: &mut PyClassShell) -> PyResult> { + fn __next__(mut slf: PyRefMut) -> PyResult> { Ok(slf.iter.next()) } } @@ -231,6 +231,7 @@ fn callable() { } #[pyclass] +#[derive(Debug)] struct SetItem { key: i32, val: i32, @@ -250,10 +251,13 @@ fn setitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyClassShell::new_ref(py, SetItem { key: 0, val: 0 }).unwrap(); + let c = PyCell::new(py, SetItem { key: 0, val: 0 }).unwrap(); py_run!(py, c, "c[1] = 2"); - assert_eq!(c.key, 1); - assert_eq!(c.val, 2); + { + let c = c.borrow(); + assert_eq!(c.key, 1); + assert_eq!(c.val, 2); + } py_expect_exception!(py, c, "del c[1]", NotImplementedError); } @@ -275,9 +279,12 @@ fn delitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyClassShell::new_ref(py, DelItem { key: 0 }).unwrap(); + let c = PyCell::new(py, DelItem { key: 0 }).unwrap(); py_run!(py, c, "del c[1]"); - assert_eq!(c.key, 1); + { + let c = c.borrow(); + assert_eq!(c.key, 1); + } py_expect_exception!(py, c, "c[1] = 2", NotImplementedError); } @@ -304,10 +311,14 @@ fn setdelitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyClassShell::new_ref(py, SetDelItem { val: None }).unwrap(); + let c = PyCell::new(py, SetDelItem { val: None }).unwrap(); py_run!(py, c, "c[1] = 2"); - assert_eq!(c.val, Some(2)); + { + let c = c.borrow(); + assert_eq!(c.val, Some(2)); + } py_run!(py, c, "del c[1]"); + let c = c.borrow(); assert_eq!(c.val, None); } @@ -383,21 +394,26 @@ fn context_manager() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyClassShell::new_mut(py, ContextManager { exit_called: false }).unwrap(); + let c = PyCell::new(py, ContextManager { exit_called: false }).unwrap(); py_run!(py, c, "with c as x: assert x == 42"); - assert!(c.exit_called); - - c.exit_called = false; + { + let mut c = c.borrow_mut(); + assert!(c.exit_called); + c.exit_called = false; + } py_run!(py, c, "with c as x: raise ValueError"); - assert!(c.exit_called); - - c.exit_called = false; + { + let mut c = c.borrow_mut(); + assert!(c.exit_called); + c.exit_called = false; + } py_expect_exception!( py, c, "with c as x: raise NotImplementedError", NotImplementedError ); + let c = c.borrow(); assert!(c.exit_called); } @@ -455,7 +471,7 @@ struct DunderDictSupport {} fn dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyClassShell::new_ref(py, DunderDictSupport {}).unwrap(); + let inst = PyCell::new(py, DunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -470,7 +486,28 @@ fn dunder_dict_support() { fn access_dunder_dict() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyClassShell::new_ref(py, DunderDictSupport {}).unwrap(); + let inst = PyCell::new(py, DunderDictSupport {}).unwrap(); + py_run!( + py, + inst, + r#" + inst.a = 1 + assert inst.__dict__ == {'a': 1} + "# + ); +} + +// If the base class has dict support, child class also has dict +#[pyclass(extends=DunderDictSupport)] +struct InheritDict { + _value: usize, +} + +#[test] +fn inherited_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst = PyCell::new(py, (InheritDict { _value: 0 }, DunderDictSupport {})).unwrap(); py_run!( py, inst, @@ -488,7 +525,7 @@ struct WeakRefDunderDictSupport {} fn weakref_dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyClassShell::new_ref(py, WeakRefDunderDictSupport {}).unwrap(); + let inst = PyCell::new(py, WeakRefDunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -513,7 +550,7 @@ impl PyObjectProtocol for ClassWithGetAttr { fn getattr_doesnt_override_member() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyClassShell::new_ref(py, ClassWithGetAttr { data: 4 }).unwrap(); + let inst = PyCell::new(py, ClassWithGetAttr { data: 4 }).unwrap(); py_assert!(py, inst, "inst.data == 4"); py_assert!(py, inst, "inst.a == 8"); } diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 8f2d0923..7225aa49 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::types::{PyAny, PyTuple}; -use pyo3::{ffi, py_run, AsPyPointer, PyClassShell}; +use pyo3::{ffi, py_run, AsPyPointer, PyCell, PyTryInto}; use std::cell::RefCell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -153,7 +153,7 @@ fn gc_integration() { { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyClassShell::new_mut( + let inst = PyCell::new( py, GCIntegration { self_ref: RefCell::new(py.None()), @@ -164,7 +164,8 @@ fn gc_integration() { ) .unwrap(); - *inst.self_ref.borrow_mut() = inst.to_object(py); + let mut borrow = inst.borrow_mut(); + *borrow.self_ref.borrow_mut() = inst.to_object(py); } let gil = Python::acquire_gil(); @@ -188,7 +189,7 @@ impl PyGCProtocol for GCIntegration2 { fn gc_integration2() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyClassShell::new_ref(py, GCIntegration2 {}).unwrap(); + let inst = PyCell::new(py, GCIntegration2 {}).unwrap(); py_run!(py, inst, "import gc; assert inst in gc.get_objects()"); } @@ -199,7 +200,25 @@ struct WeakRefSupport {} fn weakref_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyClassShell::new_ref(py, WeakRefSupport {}).unwrap(); + let inst = PyCell::new(py, WeakRefSupport {}).unwrap(); + py_run!( + py, + inst, + "import weakref; assert weakref.ref(inst)() is inst" + ); +} + +// If the base class has weakref support, child class also has weakref. +#[pyclass(extends=WeakRefSupport)] +struct InheritWeakRef { + _value: usize, +} + +#[test] +fn inherited_weakref() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let inst = PyCell::new(py, (InheritWeakRef { _value: 0 }, WeakRefSupport {})).unwrap(); py_run!( py, inst, @@ -264,12 +283,10 @@ fn inheritance_with_new_methods_with_drop() { let typeobj = py.get_type::(); let inst = typeobj.call((), None).unwrap(); - let obj = SubClassWithDrop::try_from_mut(inst).unwrap(); - obj.data = Some(Arc::clone(&drop_called1)); - - let base: &mut ::BaseType = - unsafe { py.mut_from_borrowed_ptr(inst.as_ptr()) }; - base.data = Some(Arc::clone(&drop_called2)); + let obj: &PyCell = inst.try_into().unwrap(); + let mut obj_ref_mut = obj.borrow_mut(); + obj_ref_mut.data = Some(Arc::clone(&drop_called1)); + obj_ref_mut.as_mut().data = Some(Arc::clone(&drop_called2)); } assert!(drop_called1.load(Ordering::Relaxed)); diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index f19d0fd1..f4bb4dfd 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -36,6 +36,14 @@ impl BaseClass { fn new() -> Self { BaseClass { val1: 10 } } + fn base_method(&self, x: usize) -> usize { + x * self.val1 + } + fn base_set(&mut self, fn_: &pyo3::types::PyAny) -> PyResult<()> { + let value: usize = fn_.call0()?.extract()?; + self.val1 = value; + Ok(()) + } } #[pyclass(extends=BaseClass)] @@ -50,6 +58,13 @@ impl SubClass { fn new() -> (Self, BaseClass) { (SubClass { val2: 5 }, BaseClass { val1: 10 }) } + fn sub_method(&self, x: usize) -> usize { + x * self.val2 + } + fn sub_set_and_ret(&mut self, x: usize) -> usize { + self.val2 = x; + x + } } #[test] @@ -61,6 +76,33 @@ fn inheritance_with_new_methods() { py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5"); } +#[test] +fn call_base_and_sub_methods() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = PyCell::new(py, SubClass::new()).unwrap(); + py_run!( + py, + obj, + r#" + assert obj.base_method(10) == 100 + assert obj.sub_method(10) == 50 +"# + ); +} + +#[test] +fn mutation_fails() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let obj = PyCell::new(py, SubClass::new()).unwrap(); + let global = Some([("obj", obj)].into_py_dict(py)); + let e = py + .run("obj.base_set(lambda: obj.sub_set_and_ret(1))", global, None) + .unwrap_err(); + assert!(e.is_instance::(py)) +} + #[pyclass] struct BaseClassWithResult { _val: usize, @@ -109,6 +151,7 @@ except Exception as e: } #[pyclass(extends=PySet)] +#[derive(Debug)] struct SetWithName { #[pyo3(get(name))] _name: &'static str, @@ -126,7 +169,7 @@ impl SetWithName { fn inherit_set() { let gil = Python::acquire_gil(); let py = gil.python(); - let set_sub = pyo3::pyclass::PyClassShell::new_ref(py, SetWithName::new()).unwrap(); + let set_sub = pyo3::PyCell::new(py, SetWithName::new()).unwrap(); py_run!( py, set_sub, @@ -135,6 +178,7 @@ fn inherit_set() { } #[pyclass(extends=PyDict)] +#[derive(Debug)] struct DictWithName { #[pyo3(get(name))] _name: &'static str, @@ -152,7 +196,7 @@ impl DictWithName { fn inherit_dict() { let gil = Python::acquire_gil(); let py = gil.python(); - let dict_sub = pyo3::pyclass::PyClassShell::new_ref(py, DictWithName::new()).unwrap(); + let dict_sub = pyo3::PyCell::new(py, DictWithName::new()).unwrap(); py_run!( py, dict_sub, diff --git a/tests/test_methods.rs b/tests/test_methods.rs index a23942da..24a5d3bf 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -1,7 +1,7 @@ use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType}; -use pyo3::PyClassShell; +use pyo3::PyCell; mod common; @@ -23,8 +23,9 @@ fn instance_method() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyClassShell::new_mut(py, InstanceMethod { member: 42 }).unwrap(); - assert_eq!(obj.method().unwrap(), 42); + let obj = PyCell::new(py, InstanceMethod { member: 42 }).unwrap(); + let obj_ref = obj.borrow(); + assert_eq!(obj_ref.method().unwrap(), 42); let d = [("obj", obj)].into_py_dict(py); py.run("assert obj.method() == 42", None, Some(d)).unwrap(); py.run("assert obj.method.__doc__ == 'Test method'", None, Some(d)) @@ -49,8 +50,9 @@ fn instance_method_with_args() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyClassShell::new_mut(py, InstanceMethodWithArgs { member: 7 }).unwrap(); - assert_eq!(obj.method(6).unwrap(), 42); + let obj = PyCell::new(py, InstanceMethodWithArgs { member: 7 }).unwrap(); + let obj_ref = obj.borrow(); + assert_eq!(obj_ref.method(6).unwrap(), 42); let d = [("obj", obj)].into_py_dict(py); py.run("assert obj.method(3) == 21", None, Some(d)).unwrap(); py.run("assert obj.method(multiplier=6) == 42", None, Some(d)) @@ -395,7 +397,7 @@ impl MethodWithLifeTime { fn method_with_lifetime() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyClassShell::new_ref(py, MethodWithLifeTime {}).unwrap(); + let obj = PyCell::new(py, MethodWithLifeTime {}).unwrap(); py_run!( py, obj, diff --git a/tests/test_pyself.rs b/tests/test_pyself.rs index ae7ce01f..66fa1f4e 100644 --- a/tests/test_pyself.rs +++ b/tests/test_pyself.rs @@ -2,7 +2,7 @@ use pyo3; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyString}; -use pyo3::{AsPyRef, PyClassShell, PyIterProtocol}; +use pyo3::{AsPyRef, PyCell, PyIterProtocol}; use std::collections::HashMap; mod common; @@ -17,16 +17,13 @@ struct Reader { #[pymethods] impl Reader { - fn clone_ref(slf: &PyClassShell) -> &PyClassShell { + fn clone_ref(slf: &PyCell) -> &PyCell { slf } - fn clone_ref_with_py<'py>( - slf: &'py PyClassShell, - _py: Python<'py>, - ) -> &'py PyClassShell { + fn clone_ref_with_py<'py>(slf: &'py PyCell, _py: Python<'py>) -> &'py PyCell { slf } - fn get_iter(slf: &PyClassShell, keys: Py) -> PyResult { + fn get_iter(slf: &PyCell, keys: Py) -> PyResult { Ok(Iter { reader: slf.into(), keys, @@ -34,7 +31,7 @@ impl Reader { }) } fn get_iter_and_reset( - slf: &mut PyClassShell, + mut slf: PyRefMut, keys: Py, py: Python, ) -> PyResult { @@ -49,6 +46,7 @@ impl Reader { } #[pyclass] +#[derive(Debug)] struct Iter { reader: Py, keys: Py, @@ -57,23 +55,23 @@ struct Iter { #[pyproto] impl PyIterProtocol for Iter { - fn __iter__(slf: &mut PyClassShell) -> PyResult { + fn __iter__(slf: PyRefMut) -> PyResult { let py = unsafe { Python::assume_gil_acquired() }; - Ok(slf.to_object(py)) + Ok(slf.into_py(py)) } - fn __next__(slf: &mut PyClassShell) -> PyResult> { + fn __next__(mut slf: PyRefMut) -> PyResult> { let py = unsafe { Python::assume_gil_acquired() }; let bytes = slf.keys.as_ref(py).as_bytes(); match bytes.get(slf.idx) { Some(&b) => { - let res = slf - .reader - .as_ref(py) + slf.idx += 1; + let reader = slf.reader.as_ref(py); + let reader_ref = reader.try_borrow()?; + let res = reader_ref .inner .get(&b) .map(|s| PyString::new(py, s).into()); - slf.idx += 1; Ok(res) } None => Ok(None), @@ -89,7 +87,7 @@ fn reader() -> Reader { } #[test] -fn test_nested_iter1() { +fn test_nested_iter() { let gil = Python::acquire_gil(); let py = gil.python(); let reader: PyObject = reader().into_py(py); @@ -113,11 +111,12 @@ fn test_clone_ref() { fn test_nested_iter_reset() { let gil = Python::acquire_gil(); let py = gil.python(); - let reader = PyClassShell::new_ref(py, reader()).unwrap(); + let reader = PyCell::new(py, reader()).unwrap(); py_assert!( py, reader, "list(reader.get_iter_and_reset(bytes([3, 5, 2]))) == ['c', 'e', 'b']" ); - assert!(reader.inner.is_empty()); + let reader_ref = reader.borrow(); + assert!(reader_ref.inner.is_empty()); } diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index 9b97aa6b..500bf5b0 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -64,7 +64,7 @@ impl PySequenceProtocol for ByteSequence { } } - fn __concat__(&self, other: &Self) -> PyResult { + fn __concat__(&self, other: PyRef<'p, Self>) -> PyResult { let mut elements = self.elements.clone(); elements.extend_from_slice(&other.elements); Ok(Self { elements }) diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index 6406cbf5..85211a34 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -1,5 +1,5 @@ use pyo3::prelude::*; -use pyo3::{types::PyType, wrap_pyfunction, wrap_pymodule, PyClassShell}; +use pyo3::{types::PyType, wrap_pyfunction, wrap_pymodule, PyCell}; mod common; @@ -144,7 +144,7 @@ fn test_methods() { let _ = a; } #[text_signature = "($self, b)"] - fn pyself_method(_this: &PyClassShell, b: i32) { + fn pyself_method(_this: &PyCell, b: i32) { let _ = b; } #[classmethod] diff --git a/tests/test_various.rs b/tests/test_various.rs index e24db369..99708e0e 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -1,7 +1,7 @@ use pyo3::prelude::*; use pyo3::types::IntoPyDict; use pyo3::types::{PyDict, PyTuple}; -use pyo3::{py_run, wrap_pyfunction, AsPyRef, PyClassShell}; +use pyo3::{py_run, wrap_pyfunction, AsPyRef, PyCell}; mod common; @@ -15,7 +15,7 @@ impl MutRefArg { fn get(&self) -> PyResult { Ok(self.n) } - fn set_other(&self, other: &mut MutRefArg) -> PyResult<()> { + fn set_other(&self, mut other: PyRefMut) -> PyResult<()> { other.n = 100; Ok(()) } @@ -31,7 +31,8 @@ fn mut_ref_arg() { let d = [("inst1", &inst1), ("inst2", &inst2)].into_py_dict(py); py.run("inst1.set_other(inst2)", None, Some(d)).unwrap(); - assert_eq!(inst2.as_ref(py).n, 100); + let inst2 = inst2.as_ref(py).borrow(); + assert_eq!(inst2.n, 100); } #[pyclass] @@ -81,8 +82,8 @@ fn intopytuple_pyclass() { let py = gil.python(); let tup = ( - PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), - PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), + PyCell::new(py, SimplePyClass {}).unwrap(), + PyCell::new(py, SimplePyClass {}).unwrap(), ); py_assert!(py, tup, "type(tup[0]).__name__ == 'SimplePyClass'"); py_assert!(py, tup, "type(tup[0]).__name__ == type(tup[1]).__name__"); @@ -106,8 +107,8 @@ fn pytuple_pyclass_iter() { let tup = PyTuple::new( py, [ - PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), - PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), + PyCell::new(py, SimplePyClass {}).unwrap(), + PyCell::new(py, SimplePyClass {}).unwrap(), ] .iter(), ); @@ -127,7 +128,7 @@ impl PickleSupport { } pub fn __reduce__<'py>( - slf: &'py PyClassShell, + slf: &'py PyCell, py: Python<'py>, ) -> PyResult<(PyObject, &'py PyTuple, PyObject)> { let cls = slf.to_object(py).getattr(py, "__class__")?; @@ -141,7 +142,7 @@ fn add_module(py: Python, module: &PyModule) -> PyResult<()> { .dict() .get_item("modules") .unwrap() - .downcast_mut::()? + .downcast::()? .set_item(module.name()?, module) } @@ -152,7 +153,7 @@ fn test_pickle() { let module = PyModule::new(py, "test_module").unwrap(); module.add_class::().unwrap(); add_module(py, module).unwrap(); - let inst = PyClassShell::new_ref(py, PickleSupport {}).unwrap(); + let inst = PyCell::new(py, PickleSupport {}).unwrap(); py_run!( py, inst, diff --git a/tests/ui/missing_clone.stderr b/tests/ui/missing_clone.stderr index 54452e43..bb243766 100644 --- a/tests/ui/missing_clone.stderr +++ b/tests/ui/missing_clone.stderr @@ -4,5 +4,4 @@ error[E0277]: the trait bound `TestClass: std::clone::Clone` is not satisfied 15 | let t: TestClass = pyvalue.extract(py).unwrap(); | ^^^^^^^ the trait `std::clone::Clone` is not implemented for `TestClass` | - = note: required because of the requirements on the impl of `pyo3::conversion::extract_impl::ExtractImpl<'_, TestClass>` for `pyo3::conversion::extract_impl::Cloned` = note: required because of the requirements on the impl of `pyo3::conversion::FromPyObject<'_>` for `TestClass`