diff --git a/Architecture.md b/Architecture.md index d0e873f80a8..5f676b19200 100644 --- a/Architecture.md +++ b/Architecture.md @@ -135,12 +135,12 @@ to ensure Rust's borrow rules. See [the documentation](https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyCell.html) for more. `PyCell` requires that `T` implements `PyClass`. -This trait is somewhat complex and derives many traits, but the most important one is `PyTypeObject` +This trait is somewhat complex and derives many traits, but the most important one is `PyTypeInfo` in [`src/type_object.rs`]. -`PyTypeObject` is also implemented for built-in types. +`PyTypeInfo` is also implemented for built-in types. In Python, all objects have their types, and types are also objects of `type`. For example, you can see `type({})` shows `dict` and `type(type({}))` shows `type` in Python REPL. -`T: PyTypeObject` implies that `T` has a corresponding type object. +`T: PyTypeInfo` implies that `T` has a corresponding type object. ## 4. Protocol methods @@ -153,8 +153,8 @@ implemented in Python, such as GC support. ## 5. Procedural macros to simplify usage for users. -[`pyo3-macros`] provides six proc-macro APIs: `pymodule`, `pyproto`, `pyfunction`, `pyclass`, -`pymethods`, and `#[derive(FromPyObject)]`. +[`pyo3-macros`] provides five proc-macro APIs: `pymodule`, `pyfunction`, `pyclass`, +`pymethods`, and `#[derive(FromPyObject)]`. (And a deprecated `pyproto` macro.) [`pyo3-macros-backend`] has the actual implementations of these APIs. [`src/derive_utils.rs`] contains some utilities used in code generated by these proc-macros, such as parsing function arguments. diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfece05260..5e368a1f6f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,18 @@ PyO3 versions, please see the [migration guide](https://pyo3.rs/latest/migration The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [Unreleased] ### Added - Implement `ToPyObject` for `[T; N]`. [#2313](https://github.com/PyO3/pyo3/pull/2313) +### Changed + +- Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2287](https://github.com/PyO3/pyo3/pull/2287) +- The deprecated `pyproto` feature is now disabled by default. [#2322](https://github.com/PyO3/pyo3/pull/2322) +- Deprecate `ToBorrowedObject` trait (it is only used as a wrapper for `ToPyObject`). [#2333](https://github.com/PyO3/pyo3/pull/2333) + ## [0.16.4] - 2022-04-14 ### Added @@ -399,7 +405,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Remove FFI definition `PyCFunction_ClearFreeList` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425) -- `PYO3_CROSS_LIB_DIR` enviroment variable no long required when compiling for x86-64 Python from macOS arm64 and reverse. [#1428](https://github.com/PyO3/pyo3/pull/1428) +- `PYO3_CROSS_LIB_DIR` environment variable no long required when compiling for x86-64 Python from macOS arm64 and reverse. [#1428](https://github.com/PyO3/pyo3/pull/1428) - Fix FFI definition `_PyEval_RequestCodeExtraIndex`, which took an argument of the wrong type. [#1429](https://github.com/PyO3/pyo3/pull/1429) - Fix FFI definition `PyIndex_Check` missing with the `abi3` feature. [#1436](https://github.com/PyO3/pyo3/pull/1436) - Fix incorrect `TypeError` raised when keyword-only argument passed along with a positional argument in `*args`. [#1440](https://github.com/PyO3/pyo3/pull/1440) @@ -408,7 +414,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - No longer include `__doc__` in `__all__` generated for `#[pymodule]`. [#1509](https://github.com/PyO3/pyo3/pull/1509) - Always use cross-compiling configuration if any of the `PYO3_CROSS` family of environment variables are set. [#1514](https://github.com/PyO3/pyo3/pull/1514) - Support `EnvironmentError`, `IOError`, and `WindowsError` on PyPy. [#1533](https://github.com/PyO3/pyo3/pull/1533) -- Fix unneccessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557) +- Fix unnecessary rebuilds when cycling between `cargo check` and `cargo clippy` in a Python virtualenv. [#1557](https://github.com/PyO3/pyo3/pull/1557) - Fix segfault when dereferencing `ffi::PyDateTimeAPI` without the GIL. [#1563](https://github.com/PyO3/pyo3/pull/1563) - Fix memory leak in `FromPyObject` implementations for `u128` and `i128`. [#1638](https://github.com/PyO3/pyo3/pull/1638) - Fix `#[pyclass(extends=PyDict)]` leaking the dict contents on drop. [#1657](https://github.com/PyO3/pyo3/pull/1657) @@ -992,7 +998,7 @@ Yanked ### Changed - Removes the types from the root module and the prelude. They now live in `pyo3::types` instead. -- All exceptions are consturcted with `py_err` instead of `new`, as they return `PyErr` and not `Self`. +- All exceptions are constructed with `py_err` instead of `new`, as they return `PyErr` and not `Self`. - `as_mut` and friends take and `&mut self` instead of `&self` - `ObjectProtocol::call` now takes an `Option<&PyDict>` for the kwargs instead of an `IntoPyDictPointer`. - `IntoPyDictPointer` was replace by `IntoPyDict` which doesn't convert `PyDict` itself anymore and returns a `PyDict` instead of `*mut PyObject`. @@ -1162,7 +1168,7 @@ Yanked - Initial release [Unreleased]: https://github.com/pyo3/pyo3/compare/v0.16.4...HEAD -[0.16.3]: https://github.com/pyo3/pyo3/compare/v0.16.3...v0.16.4 +[0.16.4]: https://github.com/pyo3/pyo3/compare/v0.16.3...v0.16.4 [0.16.3]: https://github.com/pyo3/pyo3/compare/v0.16.2...v0.16.3 [0.16.2]: https://github.com/pyo3/pyo3/compare/v0.16.1...v0.16.2 [0.16.1]: https://github.com/pyo3/pyo3/compare/v0.16.0...v0.16.1 diff --git a/Cargo.toml b/Cargo.toml index 5b91293d9d8..95401ea39cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ cfg-if = "1.0" libc = "0.2.62" parking_lot = ">= 0.11, < 0.13" -# ffi bindings to the python interpreter, split into a seperate crate so they can be used independently +# ffi bindings to the python interpreter, split into a separate crate so they can be used independently pyo3-ffi = { path = "pyo3-ffi", version = "=0.16.4" } # support crates for macros feature @@ -53,7 +53,7 @@ serde_json = "1.0.61" pyo3-build-config = { path = "pyo3-build-config", version = "0.16.4", features = ["resolve-config"] } [features] -default = ["macros", "pyproto"] +default = ["macros"] # Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc. macros = ["pyo3-macros", "indoc", "unindent"] diff --git a/guide/src/class.md b/guide/src/class.md index 73350b9d74e..d5a0c12c739 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,7 +52,7 @@ enum MyEnum { Because Python objects are freely shared between threads by the Python interpreter, all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)). -The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`], and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. +The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ## Constructor @@ -880,7 +880,7 @@ Python::with_gil(|py| { }) ``` -All methods defined by PyO3 can be overriden. For example here's how you override `__repr__`: +All methods defined by PyO3 can be overridden. For example here's how you override `__repr__`: ```rust # use pyo3::prelude::*; @@ -940,19 +940,14 @@ The `#[pyclass]` macro expands to roughly the code seen below. The `PyClassImplC # #[cfg(not(feature = "multiple-pymethods"))] { # use pyo3::prelude::*; // Note: the implementation differs slightly with the `multiple-pymethods` feature enabled. - -/// Class for demonstration struct MyClass { # #[allow(dead_code)] num: i32, } - -unsafe impl pyo3::PyTypeInfo for MyClass { - type AsRefTarget = PyCell; - +unsafe impl ::pyo3::type_object::PyTypeInfo for MyClass { + type AsRefTarget = ::pyo3::PyCell; const NAME: &'static str = "MyClass"; - const MODULE: Option<&'static str> = None; - + const MODULE: ::std::option::Option<&'static str> = ::std::option::Option::None; #[inline] fn type_object_raw(py: pyo3::Python<'_>) -> *mut pyo3::ffi::PyTypeObject { use pyo3::type_object::LazyStaticType; @@ -961,10 +956,14 @@ unsafe impl pyo3::PyTypeInfo for MyClass { } } -impl pyo3::pyclass::PyClass for MyClass { - type Dict = pyo3::impl_::pyclass::PyClassDummySlot; - type WeakRef = pyo3::impl_::pyclass::PyClassDummySlot; - type BaseNativeType = PyAny; +impl ::pyo3::PyClass for MyClass { } + +impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut MyClass { + type Target = ::pyo3::PyRefMut<'a, MyClass>; +} + +impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a MyClass { + type Target = ::pyo3::PyRef<'a, MyClass>; } impl pyo3::IntoPy for MyClass { @@ -980,6 +979,11 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { type Layout = PyCell; type BaseType = PyAny; type ThreadChecker = pyo3::impl_::pyclass::ThreadCheckerStub; + type Mutability = pyo3::pycell::Mutable; + type PyClassMutability = pyo3::pycell::MutableClass; + type Dict = ::pyo3::impl_::pyclass::PyClassDummySlot; + type WeakRef = ::pyo3::impl_::pyclass::PyClassDummySlot; + type BaseNativeType = ::pyo3::PyAny; fn for_all_items(visitor: &mut dyn FnMut(&pyo3::impl_::pyclass::PyClassItems)) { use pyo3::impl_::pyclass::*; @@ -989,6 +993,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { visitor(collector.py_methods()); } } + # Python::with_gil(|py| { # let cls = py.get_type::(); # pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") @@ -999,7 +1004,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { [`GILGuard`]: {{#PYO3_DOCS_URL}}/pyo3/struct.GILGuard.html [`PyTypeInfo`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeInfo.html -[`PyTypeObject`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeObject.html [`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html [`PyClass`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/trait.PyClass.html diff --git a/guide/src/class/numeric.md b/guide/src/class/numeric.md index a93d2239add..df3c908fa7e 100644 --- a/guide/src/class/numeric.md +++ b/guide/src/class/numeric.md @@ -7,7 +7,7 @@ Before proceeding, we should think about how we want to handle overflows. There be reinventing the wheel. - We can raise exceptions whenever `Number` overflows, but that makes the API painful to use. - We can wrap around the boundary of `i32`. This is the approach we'll take here. To do that we'll just forward to `i32`'s - `wrapping_*` methods. + `wrapping_*` methods. ### Fixing our constructor @@ -124,7 +124,7 @@ impl Number { } ``` -### Unary arithmethic operations +### Unary arithmetic operations ```rust # use pyo3::prelude::*; @@ -336,39 +336,39 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { # def hash_djb2(s: str): # n = Number(0) # five = Number(5) -# +# # for x in s: # n = Number(ord(x)) + ((n << five) - n) # return n -# +# # assert hash_djb2('l50_50') == Number(-1152549421) # assert hash_djb2('logo') == Number(3327403) # assert hash_djb2('horizon') == Number(1097468315) -# -# +# +# # assert Number(2) + Number(2) == Number(4) # assert Number(2) + Number(2) != Number(5) -# +# # assert Number(13) - Number(7) == Number(6) # assert Number(13) - Number(-7) == Number(20) -# +# # assert Number(13) / Number(7) == Number(1) # assert Number(13) // Number(7) == Number(1) -# +# # assert Number(13) * Number(7) == Number(13*7) -# +# # assert Number(13) > Number(7) # assert Number(13) < Number(20) # assert Number(13) == Number(13) # assert Number(13) >= Number(7) # assert Number(13) <= Number(20) # assert Number(13) == Number(13) -# -# +# +# # assert (True if Number(1) else False) # assert (False if Number(0) else True) -# -# +# +# # assert int(Number(13)) == 13 # assert float(Number(13)) == 13 # assert Number.__doc__ == "Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.\nIt's not a story C would tell you. It's a Rust legend." @@ -383,14 +383,14 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { # assert Number(1337).__repr__() == 'Number(1337)' "#; -# -# use pyo3::type_object::PyTypeObject; -# +# +# use pyo3::PyTypeInfo; +# # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { # let globals = PyModule::import(py, "__main__")?.dict(); # globals.set_item("Number", Number::type_object(py))?; -# +# # py.run(SCRIPT, Some(globals), None)?; # Ok(()) # }) @@ -446,4 +446,4 @@ fn wrap(obj: &PyAny) -> Result { [`PyErr::take`]: https://docs.rs/pyo3/latest/pyo3/prelude/struct.PyErr.html#method.take [`Python`]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html [`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html -[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html \ No newline at end of file +[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html diff --git a/guide/src/class/protocols.md b/guide/src/class/protocols.md index 88e76d64372..716f45d51f6 100644 --- a/guide/src/class/protocols.md +++ b/guide/src/class/protocols.md @@ -4,8 +4,8 @@ Python's object model defines several protocols for different object behavior, s In the Python C-API which PyO3 is implemented upon, many of these magic methods have to be placed into special "slots" on the class type object, as covered in the previous section. There are two ways in which this can be done: - - [New in PyO3 0.15, recommended in PyO3 0.16] In `#[pymethods]`, if the name of the method is a recognised magic method, PyO3 will place it in the type object automatically. - - [Deprecated in PyO3 0.16] In special traits combined with the `#[pyproto]` attribute. + - In `#[pymethods]`, if the name of the method is a recognised magic method, PyO3 will place it in the type object automatically. + - [Deprecated since PyO3 0.16] In special traits combined with the `#[pyproto]` attribute. (There are also many magic methods which don't have a special slot, such as `__dir__`. These methods can be implemented as normal in `#[pymethods]`.) @@ -404,7 +404,7 @@ impl ClassWithGCSupport { PyO3 can use the `#[pyproto]` attribute in combination with special traits to implement the magic methods which need slots. The special traits are listed below. See also the [documentation for the `pyo3::class` module]({{#PYO3_DOCS_URL}}/pyo3/class/index.html). -Due to complexity in the implementation and usage, these traits are deprecated in PyO3 0.16 in favour of the `#[pymethods]` solution. +Due to complexity in the implementation and usage, these traits were deprecated in PyO3 0.16 in favour of the `#[pymethods]` solution. All `#[pyproto]` methods can return `T` instead of `PyResult` if the method implementation is infallible. In addition, if the return type is `()`, it can be omitted altogether. diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 2705413d104..1bfcc56f596 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -215,7 +215,7 @@ struct RustyTransparentStruct { #### Deriving [`FromPyObject`] for enums The `FromPyObject` derivation for enums generates code that tries to extract the variants in the -order of the fields. As soon as a variant can be extracted succesfully, that variant is returned. +order of the fields. As soon as a variant can be extracted successfully, that variant is returned. This makes it possible to extract Python union types like `str | int`. The same customizations and restrictions described for struct derivations apply to enum variants, diff --git a/guide/src/faq.md b/guide/src/faq.md index 6ef7fd6705a..d5773abc28c 100644 --- a/guide/src/faq.md +++ b/guide/src/faq.md @@ -8,7 +8,7 @@ 2. The initialization code calls some Python API which temporarily releases the GIL e.g. `Python::import`. 3. Another thread (thread B) acquires the Python GIL and attempts to access the same `lazy_static` value. 4. Thread B is blocked, because it waits for `lazy_static`'s initialization to lock to release. -5. Thread A is blocked, because it waits to re-aquire the GIL which thread B still holds. +5. Thread A is blocked, because it waits to re-acquire the GIL which thread B still holds. 6. Deadlock. PyO3 provides a struct [`GILOnceCell`] which works equivalently to `OnceCell` but relies solely on the Python GIL for thread safety. This means it can be used in place of `lazy_static` or `once_cell` where you are experiencing the deadlock described above. See the documentation for [`GILOnceCell`] for an example how to use it. diff --git a/guide/src/features.md b/guide/src/features.md index d37059582da..c67de97ec30 100644 --- a/guide/src/features.md +++ b/guide/src/features.md @@ -59,7 +59,6 @@ This feature enables a dependency on the `pyo3-macros` crate, which provides the - `#[pyfunction]` - `#[pyclass]` - `#[pymethods]` -- `#[pyproto]` - `#[derive(FromPyObject)]` It also provides the `py_run!` macro. @@ -78,9 +77,7 @@ See [the `#[pyclass]` implementation details](class.md#implementation-details) f ### `pyproto` -This feature enables the `#[pyproto]` macro, which is an alternative (older, soon-to-be-deprecated) to `#[pymethods]` for defining magic methods such as `__eq__`. - -> This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml. +This feature enables the `#[pyproto]` macro, which is a deprecated alternative to `#[pymethods]` for defining magic methods such as `__eq__`. ### `nightly` diff --git a/guide/src/migration.md b/guide/src/migration.md index 7a763e667c3..3aa50f7f218 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -3,6 +3,43 @@ This guide can help you upgrade code through breaking changes from one PyO3 version to the next. For a detailed list of all changes, see the [CHANGELOG](changelog.md). +## from 0.16.* to 0.17 + +### The `pyproto` feature is now disabled by default + +In preparation for removing the deprecated `#[pyproto]` attribute macro in a future PyO3 version, it is now gated behind an opt-in feature flag. This also gives a slight saving to compile times for code which does not use the deprecated macro. + +### `PyTypeObject` trait has been deprecated + +The `PyTypeObject` trait already was near-useless; almost all functionality was already on the `PyTypeInfo` trait, which `PyTypeObject` had a blanket implementation based upon. In PyO3 0.17 the final method, `PyTypeObject::type_object` was moved to `PyTypeInfo::type_object`. + +To migrate, update trait bounds and imports from `PyTypeObject` to `PyTypeInfo`. + +Before: + +```rust,ignore +use pyo3::Python; +use pyo3::type_object::PyTypeObject; +use pyo3::types::PyType; + +fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) +} +``` + +After + +```rust +use pyo3::{Python, PyTypeInfo}; +use pyo3::types::PyType; + +fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) +} + +# Python::with_gil(|py| { get_type_object::(py); }); +``` + ## from 0.15.* to 0.16 ### Drop support for older technologies diff --git a/pyo3-macros-backend/src/attributes.rs b/pyo3-macros-backend/src/attributes.rs index 6d381e21933..66e1a4e1891 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -29,6 +29,7 @@ pub mod kw { syn::custom_keyword!(transparent); syn::custom_keyword!(unsendable); syn::custom_keyword!(weakref); + syn::custom_keyword!(immutable); } #[derive(Clone, Debug)] diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 3a7d943f401..d421acf5d9e 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -54,6 +54,7 @@ pub struct PyClassPyO3Options { pub dict: Option, pub extends: Option, pub freelist: Option, + pub immutable: Option, pub mapping: Option, pub module: Option, pub name: Option, @@ -70,6 +71,7 @@ enum PyClassPyO3Option { Dict(kw::dict), Extends(ExtendsAttribute), Freelist(FreelistAttribute), + Immutable(kw::immutable), Mapping(kw::mapping), Module(ModuleAttribute), Name(NameAttribute), @@ -92,6 +94,8 @@ impl Parse for PyClassPyO3Option { input.parse().map(PyClassPyO3Option::Extends) } else if lookahead.peek(attributes::kw::freelist) { input.parse().map(PyClassPyO3Option::Freelist) + } else if lookahead.peek(attributes::kw::immutable) { + input.parse().map(PyClassPyO3Option::Immutable) } else if lookahead.peek(attributes::kw::mapping) { input.parse().map(PyClassPyO3Option::Mapping) } else if lookahead.peek(attributes::kw::module) { @@ -149,6 +153,7 @@ impl PyClassPyO3Options { PyClassPyO3Option::Dict(dict) => set_option!(dict), PyClassPyO3Option::Extends(extends) => set_option!(extends), PyClassPyO3Option::Freelist(freelist) => set_option!(freelist), + PyClassPyO3Option::Immutable(immutable) => set_option!(immutable), PyClassPyO3Option::Mapping(mapping) => set_option!(mapping), PyClassPyO3Option::Module(module) => set_option!(module), PyClassPyO3Option::Name(name) => set_option!(name), @@ -684,44 +689,31 @@ impl<'a> PyClassImplsBuilder<'a> { fn impl_pyclass(&self) -> TokenStream { let cls = self.cls; - let attr = self.attr; - let dict = if attr.options.dict.is_some() { - quote! { _pyo3::impl_::pyclass::PyClassDictSlot } - } else { - quote! { _pyo3::impl_::pyclass::PyClassDummySlot } - }; - - // insert space for weak ref - let weakref = if attr.options.weakref.is_some() { - quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot } - } else { - quote! { _pyo3::impl_::pyclass::PyClassDummySlot } - }; - let base_nativetype = if attr.options.extends.is_some() { - quote! { ::BaseNativeType } - } else { - quote! { _pyo3::PyAny } - }; quote! { - impl _pyo3::PyClass for #cls { - type Dict = #dict; - type WeakRef = #weakref; - type BaseNativeType = #base_nativetype; - } + impl _pyo3::PyClass for #cls { } } } fn impl_extractext(&self) -> TokenStream { let cls = self.cls; - quote! { - impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls - { - type Target = _pyo3::PyRef<'a, #cls>; + if self.attr.options.immutable.is_some() { + quote! { + impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls + { + type Target = _pyo3::PyRef<'a, #cls>; + } } + } else { + quote! { + impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls + { + type Target = _pyo3::PyRef<'a, #cls>; + } - impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls - { - type Target = _pyo3::PyRefMut<'a, #cls>; + impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls + { + type Target = _pyo3::PyRefMut<'a, #cls>; + } } } } @@ -831,6 +823,47 @@ impl<'a> PyClassImplsBuilder<'a> { let deprecations = &self.attr.deprecations; + let mutability = if self.attr.options.immutable.is_some() { + quote! { + _pyo3::pycell::Immutable + } + } else { + quote! { + _pyo3::pycell::Mutable + } + }; + + let class_mutability = if self.attr.options.immutable.is_some() { + quote! { + ImmutableChild + } + } else { + quote! { + MutableChild + } + }; + + let cls = self.cls; + let attr = self.attr; + let dict = if attr.options.dict.is_some() { + quote! { _pyo3::impl_::pyclass::PyClassDictSlot } + } else { + quote! { _pyo3::impl_::pyclass::PyClassDummySlot } + }; + + // insert space for weak ref + let weakref = if attr.options.weakref.is_some() { + quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot } + } else { + quote! { _pyo3::impl_::pyclass::PyClassDummySlot } + }; + + let base_nativetype = if attr.options.extends.is_some() { + quote! { ::BaseNativeType } + } else { + quote! { _pyo3::PyAny } + }; + quote! { impl _pyo3::impl_::pyclass::PyClassImpl for #cls { const DOC: &'static str = #doc; @@ -842,6 +875,11 @@ impl<'a> PyClassImplsBuilder<'a> { type BaseType = #base; type ThreadChecker = #thread_checker; #inventory + type Mutability = #mutability; + type PyClassMutability = <<#base as _pyo3::impl_::pyclass::PyClassBaseType>::PyClassMutability as _pyo3::pycell::PyClassMutability>::#class_mutability; + type Dict = #dict; + type WeakRef = #weakref; + type BaseNativeType = #base_nativetype; fn for_all_items(visitor: &mut dyn ::std::ops::FnMut(& _pyo3::impl_::pyclass::PyClassItems)) { use _pyo3::impl_::pyclass::*; diff --git a/pyo3-macros/src/lib.rs b/pyo3-macros/src/lib.rs index 3577b9c23e9..84160af2b95 100644 --- a/pyo3-macros/src/lib.rs +++ b/pyo3-macros/src/lib.rs @@ -58,7 +58,7 @@ pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream { /// A proc macro used to implement Python's [dunder methods][1]. /// -/// This atribute is required on blocks implementing [`PyObjectProtocol`][2], +/// This attribute is required on blocks implementing [`PyObjectProtocol`][2], /// [`PyNumberProtocol`][3], [`PyGCProtocol`][4] and [`PyIterProtocol`][5]. /// /// [1]: https://docs.python.org/3/reference/datamodel.html#special-method-names diff --git a/src/callback.rs b/src/callback.rs index 81fb4d1d5b1..8d3614cf9d3 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -71,7 +71,10 @@ where { #[inline] fn convert(self, py: Python<'_>) -> PyResult { - self.map_err(Into::into).and_then(|t| t.convert(py)) + match self { + Ok(v) => v.convert(py), + Err(e) => Err(e.into()), + } } } diff --git a/src/class/basic.rs b/src/class/basic.rs index 2bb92b1f4d2..be70f8eecc0 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -10,7 +10,9 @@ //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; -use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject}; +use crate::{ + exceptions, ffi, pyclass::MutablePyClass, FromPyObject, PyAny, PyCell, PyClass, PyObject, +}; use std::os::raw::c_int; /// Basic Python class customization @@ -26,14 +28,14 @@ pub trait PyObjectProtocol<'p>: PyClass { fn __setattr__(&'p mut self, name: Self::Name, value: Self::Value) -> Self::Result where - Self: PyObjectSetAttrProtocol<'p>, + Self: PyObjectSetAttrProtocol<'p> + MutablePyClass, { unimplemented!() } fn __delattr__(&'p mut self, name: Self::Name) -> Self::Result where - Self: PyObjectDelAttrProtocol<'p>, + Self: PyObjectDelAttrProtocol<'p> + MutablePyClass, { unimplemented!() } @@ -77,12 +79,12 @@ pub trait PyObjectGetAttrProtocol<'p>: PyObjectProtocol<'p> { type Name: FromPyObject<'p>; type Result: IntoPyCallbackOutput; } -pub trait PyObjectSetAttrProtocol<'p>: PyObjectProtocol<'p> { +pub trait PyObjectSetAttrProtocol<'p>: PyObjectProtocol<'p> + MutablePyClass { type Name: FromPyObject<'p>; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> { +pub trait PyObjectDelAttrProtocol<'p>: PyObjectProtocol<'p> + MutablePyClass { type Name: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/buffer.rs b/src/class/buffer.rs index 4338b5f6ff8..90693813644 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -6,7 +6,7 @@ //! For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html) //! c-api use crate::callback::IntoPyCallbackOutput; -use crate::{ffi, PyCell, PyClass, PyRefMut}; +use crate::{ffi, pyclass::MutablePyClass, PyCell, PyRefMut}; use std::os::raw::c_int; /// Buffer protocol interface @@ -15,7 +15,7 @@ use std::os::raw::c_int; /// c-api. #[allow(unused_variables)] #[deprecated(since = "0.16.0", note = "prefer `#[pymethods]` to `#[pyproto]`")] -pub trait PyBufferProtocol<'p>: PyClass { +pub trait PyBufferProtocol<'p>: MutablePyClass { // No default implementations so that implementors of this trait provide both methods. fn bf_getbuffer( diff --git a/src/class/gc.rs b/src/class/gc.rs index e9e6ae4f6ad..ec4ab6a622a 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,14 +3,14 @@ //! Python GC support -use crate::{ffi, PyCell, PyClass}; +use crate::{ffi, pyclass::MutablePyClass, PyCell}; use std::os::raw::{c_int, c_void}; pub use crate::impl_::pymethods::{PyTraverseError, PyVisit}; /// GC support #[deprecated(since = "0.16.0", note = "prefer `#[pymethods]` to `#[pyproto]`")] -pub trait PyGCProtocol<'p>: PyClass { +pub trait PyGCProtocol<'p>: MutablePyClass { fn __traverse__(&'p self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>; fn __clear__(&'p mut self); } diff --git a/src/class/mapping.rs b/src/class/mapping.rs index 7a066370266..533f5178ba2 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -5,7 +5,7 @@ //! Trait and support implementation for implementing mapping support use crate::callback::IntoPyCallbackOutput; -use crate::{FromPyObject, PyClass, PyObject}; +use crate::{pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; /// Mapping interface #[allow(unused_variables)] @@ -41,7 +41,7 @@ pub trait PyMappingProtocol<'p>: PyClass { } // The following are a bunch of marker traits used to detect -// the existance of a slotted method. +// the existence of a slotted method. pub trait PyMappingLenProtocol<'p>: PyMappingProtocol<'p> { type Result: IntoPyCallbackOutput; @@ -52,13 +52,13 @@ pub trait PyMappingGetItemProtocol<'p>: PyMappingProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PyMappingSetItemProtocol<'p>: PyMappingProtocol<'p> { +pub trait PyMappingSetItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass { type Key: FromPyObject<'p>; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> { +pub trait PyMappingDelItemProtocol<'p>: PyMappingProtocol<'p> + MutablePyClass { type Key: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/number.rs b/src/class/number.rs index ab7f7410ee6..2ae7a81388d 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -5,7 +5,7 @@ //! Trait and support implementation for implementing number protocol use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; -use crate::{ffi, FromPyObject, PyClass, PyObject}; +use crate::{ffi, pyclass::MutablePyClass, FromPyObject, PyClass, PyObject}; /// Number interface #[allow(unused_variables)] @@ -461,74 +461,76 @@ pub trait PyNumberROrProtocol<'p>: PyNumberProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIAddProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberISubProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIMulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIMatmulProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberITruedivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIFloordivProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIModProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIDivmodProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIPowProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; // See https://bugs.python.org/issue36379 type Modulo: FromPyObject<'p>; } -pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> { +#[allow(clippy::upper_case_acronyms)] +pub trait PyNumberILShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIRShiftProtocol<'p>: PyNumberProtocol<'p> { +#[allow(clippy::upper_case_acronyms)] +pub trait PyNumberIRShiftProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIAndProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIXorProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> { +pub trait PyNumberIOrProtocol<'p>: PyNumberProtocol<'p> + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } diff --git a/src/class/sequence.rs b/src/class/sequence.rs index 9ea5064ddcb..8e486ef4fcd 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -6,7 +6,7 @@ use crate::callback::IntoPyCallbackOutput; use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; -use crate::{exceptions, ffi, PyAny, PyCell, PyClass, PyObject}; +use crate::{exceptions, ffi, pyclass::MutablePyClass, PyAny, PyCell, PyClass, PyObject}; use std::os::raw::c_int; /// Sequence interface @@ -78,7 +78,7 @@ pub trait PySequenceProtocol<'p>: PyClass + Sized { } // The following are a bunch of marker traits used to detect -// the existance of a slotted method. +// the existence of a slotted method. pub trait PySequenceLenProtocol<'p>: PySequenceProtocol<'p> { type Result: IntoPyCallbackOutput; @@ -89,13 +89,13 @@ pub trait PySequenceGetItemProtocol<'p>: PySequenceProtocol<'p> { type Result: IntoPyCallbackOutput; } -pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> { +pub trait PySequenceSetItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass { type Index: FromPyObject<'p> + From; type Value: FromPyObject<'p>; type Result: IntoPyCallbackOutput<()>; } -pub trait PySequenceDelItemProtocol<'p>: PySequenceProtocol<'p> { +pub trait PySequenceDelItemProtocol<'p>: PySequenceProtocol<'p> + MutablePyClass { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput<()>; } @@ -116,14 +116,14 @@ pub trait PySequenceRepeatProtocol<'p>: PySequenceProtocol<'p> { } pub trait PySequenceInplaceConcatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + 'p + PySequenceProtocol<'p> + IntoPy + MutablePyClass { type Other: FromPyObject<'p>; type Result: IntoPyCallbackOutput; } pub trait PySequenceInplaceRepeatProtocol<'p>: - PySequenceProtocol<'p> + IntoPy + 'p + PySequenceProtocol<'p> + IntoPy + MutablePyClass + 'p { type Index: FromPyObject<'p> + From; type Result: IntoPyCallbackOutput; diff --git a/src/conversion.rs b/src/conversion.rs index 6dffae77905..8a005a26b36 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -5,7 +5,8 @@ use crate::err::{self, PyDowncastError, PyResult}; use crate::type_object::PyTypeInfo; use crate::types::PyTuple; use crate::{ - ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python, + ffi, gil, pyclass::MutablePyClass, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, + PyRefMut, Python, }; use std::ptr::NonNull; @@ -74,10 +75,12 @@ pub trait ToPyObject { fn to_object(&self, py: Python<'_>) -> PyObject; } -/// This trait has two implementations: The slow one is implemented for -/// all [ToPyObject] and creates a new object using [ToPyObject::to_object], -/// while the fast one is only implemented for AsPyPointer (we know -/// that every AsPyPointer is also ToPyObject) and uses [AsPyPointer::as_ptr()] +/// A deprecated conversion trait which relied on the unstable `specialization` feature +/// of the Rust language. +#[deprecated( + since = "0.17.0", + note = "this trait is no longer used by PyO3, use ToPyObject or IntoPy" +)] pub trait ToBorrowedObject: ToPyObject { /// Converts self into a Python object and calls the specified closure /// on the native FFI pointer underlying the Python object. @@ -97,6 +100,7 @@ pub trait ToBorrowedObject: ToPyObject { } } +#[allow(deprecated)] impl ToBorrowedObject for T where T: ToPyObject {} /// Defines a conversion from a Rust type to a Python object. @@ -298,7 +302,7 @@ where impl<'a, T> FromPyObject<'a> for PyRefMut<'a, T> where - T: PyClass, + T: MutablePyClass, { fn extract(obj: &'a PyAny) -> PyResult { let cell: &PyCell = PyTryFrom::try_from(obj)?; diff --git a/src/err/err_state.rs b/src/err/err_state.rs index ecebeb5a622..6193b9cbf62 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -1,9 +1,8 @@ use crate::{ exceptions::{PyBaseException, PyTypeError}, ffi, - type_object::PyTypeObject, types::{PyTraceback, PyType}, - AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, Python, + AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, PyTypeInfo, Python, }; #[derive(Clone)] diff --git a/src/err/mod.rs b/src/err/mod.rs index eed6798ad58..2491d8921d1 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -1,15 +1,13 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::panic::PanicException; -use crate::type_object::PyTypeObject; +use crate::type_object::PyTypeInfo; use crate::types::{PyTraceback, PyType}; use crate::{ exceptions::{self, PyBaseException}, ffi, }; -use crate::{ - AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject, -}; +use crate::{AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, Python, ToPyObject}; use std::borrow::Cow; use std::cell::UnsafeCell; use std::ffi::CString; @@ -92,7 +90,7 @@ impl PyErr { #[inline] pub fn new(args: A) -> PyErr where - T: PyTypeObject, + T: PyTypeInfo, A: PyErrArguments + Send + Sync + 'static, { PyErr::from_state(PyErrState::LazyTypeAndValue { @@ -411,11 +409,11 @@ impl PyErr { /// If `exc` is a tuple, all exceptions in the tuple (and recursively in subtuples) are searched for a match. pub fn matches(&self, py: Python<'_>, exc: T) -> bool where - T: ToBorrowedObject, + T: ToPyObject, { - exc.with_borrowed_ptr(py, |exc| unsafe { - ffi::PyErr_GivenExceptionMatches(self.type_ptr(py), exc) != 0 - }) + unsafe { + ffi::PyErr_GivenExceptionMatches(self.type_ptr(py), exc.to_object(py).as_ptr()) != 0 + } } /// Returns true if the current exception is instance of `T`. @@ -428,7 +426,7 @@ impl PyErr { #[inline] pub fn is_instance_of(&self, py: Python<'_>) -> bool where - T: PyTypeObject, + T: PyTypeInfo, { self.is_instance(py, T::type_object(py)) } diff --git a/src/exceptions.rs b/src/exceptions.rs index d8d65de52ee..6f7c8233e19 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -218,7 +218,7 @@ macro_rules! create_exception { }; } -/// `impl $crate::type_object::PyTypeObject for $name` where `$name` is an +/// `impl PyTypeInfo for $name` where `$name` is an /// exception newly defined in Rust code. #[doc(hidden)] #[macro_export] diff --git a/src/gil.rs b/src/gil.rs index 8d4060085b8..1aa84d3c224 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -174,7 +174,7 @@ impl GILGuard { /// `GILGuard` will also contain a `GILPool`. pub(crate) fn acquire() -> GILGuard { // Maybe auto-initialize the GIL: - // - If auto-initialize feature set and supported, try to initalize the interpreter. + // - If auto-initialize feature set and supported, try to initialize the interpreter. // - If the auto-initialize feature is set but unsupported, emit hard errors only when the // extension-module feature is not activated - extension modules don't care about // auto-initialize so this avoids breaking existing builds. @@ -199,7 +199,7 @@ impl GILGuard { assert_ne!( ffi::Py_IsInitialized(), 0, - "The Python interpreter is not initalized and the `auto-initialize` \ + "The Python interpreter is not initialized and the `auto-initialize` \ feature is not enabled.\n\n\ Consider calling `pyo3::prepare_freethreaded_python()` before attempting \ to use Python APIs." diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index b793deef72b..aedc7792145 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -1,14 +1,12 @@ use crate::{ exceptions::PyTypeError, ffi, - type_object::PyTypeObject, types::{PyDict, PyString, PyTuple}, - FromPyObject, PyAny, PyErr, PyResult, Python, + FromPyObject, PyAny, PyErr, PyResult, PyTypeInfo, Python, }; /// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument. #[doc(hidden)] -#[inline] pub fn extract_argument<'py, T>(obj: &'py PyAny, arg_name: &str) -> PyResult where T: FromPyObject<'py>, @@ -22,7 +20,6 @@ where /// Alternative to [`extract_argument`] used for `Option` arguments (because they are implicitly treated /// as optional if at the end of the positional parameters). #[doc(hidden)] -#[inline] pub fn extract_optional_argument<'py, T>( obj: Option<&'py PyAny>, arg_name: &str, @@ -31,17 +28,13 @@ where T: FromPyObject<'py>, { match obj { - Some(obj) => match obj.extract() { - Ok(value) => Ok(value), - Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)), - }, + Some(obj) => extract_argument(obj, arg_name), None => Ok(None), } } /// Alternative to [`extract_argument`] used when the argument has a default value provided by an annotation. #[doc(hidden)] -#[inline] pub fn extract_argument_with_default<'py, T>( obj: Option<&'py PyAny>, arg_name: &str, @@ -51,10 +44,7 @@ where T: FromPyObject<'py>, { match obj { - Some(obj) => match obj.extract() { - Ok(value) => Ok(value), - Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)), - }, + Some(obj) => extract_argument(obj, arg_name), None => Ok(default()), } } @@ -64,7 +54,6 @@ where /// # Safety /// - `obj` must not be None (this helper is only used for required function arguments). #[doc(hidden)] -#[inline] pub fn from_py_with<'py, T>( obj: &'py PyAny, arg_name: &str, @@ -79,7 +68,6 @@ pub fn from_py_with<'py, T>( /// Alternative to [`extract_argument`] used when the argument has a `#[pyo3(from_py_with)]` annotation and also a default value. #[doc(hidden)] -#[inline] pub fn from_py_with_with_default<'py, T>( obj: Option<&'py PyAny>, arg_name: &str, @@ -87,10 +75,7 @@ pub fn from_py_with_with_default<'py, T>( default: impl FnOnce() -> T, ) -> PyResult { match obj { - Some(obj) => match extractor(obj) { - Ok(value) => Ok(value), - Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)), - }, + Some(obj) => from_py_with(obj, arg_name, extractor), None => Ok(default()), } } diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 48ce1d14800..60316befe27 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -2,9 +2,9 @@ use crate::{ exceptions::{PyAttributeError, PyNotImplementedError}, ffi, impl_::freelist::FreeList, - pycell::PyCellLayout, + pycell::{GetBorrowChecker, Mutability, PyCellLayout, PyClassMutability}, pyclass_init::PyObjectInit, - type_object::{PyLayout, PyTypeObject}, + type_object::PyLayout, Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, }; use std::{ @@ -162,7 +162,23 @@ pub trait PyClassImpl: Sized { type Layout: PyLayout; /// Base class - type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + type BaseType: PyTypeInfo + PyClassBaseType; + + /// Immutable or mutable + type Mutability: Mutability; + + /// Immutable or mutable + type PyClassMutability: PyClassMutability + GetBorrowChecker; + + /// Specify this class has `#[pyclass(dict)]` or not. + type Dict: PyClassDict; + + /// Specify this class has `#[pyclass(weakref)]` or not. + type WeakRef: PyClassWeakRef; + + /// The closest native ancestor. This is `PyAny` by default, and when you declare + /// `#[pyclass(extends=PyDict)]`, it's `PyDict`. + type BaseNativeType: PyTypeInfo + PyNativeType; /// This handles following two situations: /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing. @@ -866,9 +882,14 @@ impl PyClassThreadChecker for ThreadCheckerImpl { /// Thread checker for types that have `Send` and `extends=...`. /// Ensures that `T: Send` and the parent is not accessed by another thread. #[doc(hidden)] -pub struct ThreadCheckerInherited(PhantomData, U::ThreadChecker); - -impl PyClassThreadChecker for ThreadCheckerInherited { +pub struct ThreadCheckerInherited( + PhantomData, + U::ThreadChecker, +); + +impl PyClassThreadChecker + for ThreadCheckerInherited +{ fn ensure(&self) { self.1.ensure(); } @@ -884,14 +905,18 @@ pub trait PyClassBaseType: Sized { type BaseNativeType; type ThreadChecker: PyClassThreadChecker; type Initializer: PyObjectInit; + type PyClassMutability: PyClassMutability; } -/// All PyClasses can be used as a base type. +/// All mutable PyClasses can be used as a base type. +/// +/// In the future this will be extended to immutable PyClasses too. impl PyClassBaseType for T { type LayoutAsBase = crate::pycell::PyCell; type BaseNativeType = T::BaseNativeType; type ThreadChecker = T::ThreadChecker; type Initializer = crate::pyclass_init::PyClassInitializer; + type PyClassMutability = T::PyClassMutability; } /// Implementation of tp_dealloc for all pyclasses diff --git a/src/instance.rs b/src/instance.rs index a8c0997bf1f..45ab59b038e 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,12 +1,12 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use crate::conversion::{PyTryFrom, ToBorrowedObject}; +use crate::conversion::PyTryFrom; use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::gil; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::types::{PyDict, PyTuple}; use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, PyClassInitializer, - PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, + ffi, pyclass::MutablePyClass, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyAny, PyClass, + PyClassInitializer, PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject, }; use std::marker::PhantomData; use std::mem; @@ -430,7 +430,10 @@ where /// # Panics /// Panics if the value is currently mutably borrowed. For a non-panicking variant, use /// [`try_borrow_mut`](#method.try_borrow_mut). - pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> { + pub fn borrow_mut<'py>(&'py self, py: Python<'py>) -> PyRefMut<'py, T> + where + T: MutablePyClass, + { self.as_ref(py).borrow_mut() } @@ -457,7 +460,10 @@ where pub fn try_borrow_mut<'py>( &'py self, py: Python<'py>, - ) -> Result, PyBorrowMutError> { + ) -> Result, PyBorrowMutError> + where + T: MutablePyClass, + { self.as_ref(py).try_borrow_mut() } } @@ -557,9 +563,12 @@ impl Py { where N: ToPyObject, { - attr_name.with_borrowed_ptr(py, |attr_name| unsafe { - PyObject::from_owned_ptr_or_err(py, ffi::PyObject_GetAttr(self.as_ptr(), attr_name)) - }) + unsafe { + PyObject::from_owned_ptr_or_err( + py, + ffi::PyObject_GetAttr(self.as_ptr(), attr_name.to_object(py).as_ptr()), + ) + } } /// Sets an attribute value. @@ -589,11 +598,16 @@ impl Py { N: ToPyObject, V: ToPyObject, { - attr_name.with_borrowed_ptr(py, move |attr_name| { - value.with_borrowed_ptr(py, |value| unsafe { - err::error_on_minusone(py, ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value)) - }) - }) + unsafe { + err::error_on_minusone( + py, + ffi::PyObject_SetAttr( + self.as_ptr(), + attr_name.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Calls the object. @@ -650,10 +664,10 @@ impl Py { args: impl IntoPy>, kwargs: Option<&PyDict>, ) -> PyResult { - name.with_borrowed_ptr(py, |name| unsafe { + unsafe { let args = args.into_py(py).into_ptr(); let kwargs = kwargs.into_ptr(); - let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name); + let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name.to_object(py).as_ptr()); if ptr.is_null() { return Err(PyErr::fetch(py)); } @@ -662,7 +676,7 @@ impl Py { ffi::Py_XDECREF(args); ffi::Py_XDECREF(kwargs); result - }) + } } /// Calls a method on the object with only positional arguments. @@ -877,7 +891,7 @@ where impl<'a, T> std::convert::From> for Py where - T: PyClass, + T: MutablePyClass, { fn from(pyref: PyRefMut<'a, T>) -> Self { unsafe { Py::from_borrowed_ptr(pyref.py(), pyref.as_ptr()) } diff --git a/src/lib.rs b/src/lib.rs index 2ed3d3c1311..effa4f4787d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,9 +294,11 @@ //! [Features chapter of the guide]: https://pyo3.rs/latest/features.html#features-reference "Features Reference - PyO3 user guide" //! [`Ungil`]: crate::marker::Ungil pub use crate::class::*; +#[allow(deprecated)] +pub use crate::conversion::ToBorrowedObject; pub use crate::conversion::{ AsPyPointer, FromPyObject, FromPyPointer, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, - ToBorrowedObject, ToPyObject, + ToPyObject, }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult}; #[cfg(not(PyPy))] diff --git a/src/marker.rs b/src/marker.rs index f159d89e85e..fdfef0784f4 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -122,10 +122,11 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::gil::{self, GILGuard, GILPool}; use crate::impl_::not_send::NotSend; -use crate::type_object::{PyTypeInfo, PyTypeObject}; use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::version::PythonVersionInfo; -use crate::{ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom}; +use crate::{ + ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom, PyTypeInfo, +}; use std::ffi::{CStr, CString}; use std::marker::PhantomData; use std::os::raw::c_int; @@ -583,7 +584,7 @@ impl<'py> Python<'py> { /// Gets the Python type object for type `T`. pub fn get_type(self) -> &'py PyType where - T: PyTypeObject, + T: PyTypeInfo, { T::type_object(self) } @@ -797,7 +798,7 @@ impl<'py> Python<'py> { /// /// This function calls [`PyErr_CheckSignals()`][1] which in turn may call signal handlers. /// As Python's [`signal`][2] API allows users to define custom signal handlers, calling this - /// function allows arbitary Python code inside signal handlers to run. + /// function allows arbitrary Python code inside signal handlers to run. /// /// [1]: https://docs.python.org/3/c-api/exceptions.html?highlight=pyerr_checksignals#c.PyErr_CheckSignals /// [2]: https://docs.python.org/3/library/signal.html diff --git a/src/pycell.rs b/src/pycell.rs index 8e9038e9f0b..b8eb967986b 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -173,8 +173,10 @@ //! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell and the Interior Mutability Pattern - The Rust Programming Language" use crate::exceptions::PyRuntimeError; -use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; -use crate::pyclass::PyClass; +use crate::impl_::pyclass::{ + PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef, +}; +use crate::pyclass::{MutablePyClass, PyClass}; use crate::pyclass_init::PyClassInitializer; use crate::type_object::{PyLayout, PySizedLayout}; use crate::types::PyAny; @@ -187,16 +189,181 @@ use crate::{ use crate::{ffi, IntoPy, PyErr, PyNativeType, PyObject, PyResult, Python}; use std::cell::{Cell, UnsafeCell}; use std::fmt; +use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; +pub struct EmptySlot(()); +pub struct BorrowChecker(Cell); + +pub trait PyClassBorrowChecker { + fn new() -> Self; + + /// Increments immutable borrow count, if possible + fn try_borrow(&self) -> Result<(), PyBorrowError>; + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError>; + + /// Decrements immutable borrow count + fn release_borrow(&self); + /// Increments mutable borrow count, if possible + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError>; + /// Decremements mutable borrow count + fn release_borrow_mut(&self); +} + +impl PyClassBorrowChecker for EmptySlot { + #[inline] + fn new() -> Self { + Self(()) + } + + #[inline] + fn try_borrow(&self) -> Result<(), PyBorrowError> { + Ok(()) + } + + #[inline] + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { + Ok(()) + } + + #[inline] + fn release_borrow(&self) {} + + #[inline] + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { + unreachable!() + } + + #[inline] + fn release_borrow_mut(&self) { + unreachable!() + } +} + +impl PyClassBorrowChecker for BorrowChecker { + #[inline] + fn new() -> Self { + Self(Cell::new(BorrowFlag::UNUSED)) + } + + fn try_borrow(&self) -> Result<(), PyBorrowError> { + let flag = self.0.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + self.0.set(flag.increment()); + Ok(()) + } else { + Err(PyBorrowError { _private: () }) + } + } + + fn try_borrow_unguarded(&self) -> Result<(), PyBorrowError> { + let flag = self.0.get(); + if flag != BorrowFlag::HAS_MUTABLE_BORROW { + Ok(()) + } else { + Err(PyBorrowError { _private: () }) + } + } + + fn release_borrow(&self) { + let flag = self.0.get(); + self.0.set(flag.decrement()) + } + + fn try_borrow_mut(&self) -> Result<(), PyBorrowMutError> { + let flag = self.0.get(); + if flag == BorrowFlag::UNUSED { + self.0.set(BorrowFlag::HAS_MUTABLE_BORROW); + Ok(()) + } else { + Err(PyBorrowMutError { _private: () }) + } + } + + fn release_borrow_mut(&self) { + self.0.set(BorrowFlag::UNUSED) + } +} + +pub trait PyClassMutability { + // The storage for this inheritance layer. Only the first mutable class in + // an inheritance hierarchy needs to store the borrow flag. + type Storage: PyClassBorrowChecker; + // The borrow flag needed to implement this class' mutability. Empty until + // the first mutable class, at which point it is BorrowChecker and will be + // for all subclasses. + type Checker: PyClassBorrowChecker; + type ImmutableChild: PyClassMutability; + type MutableChild: PyClassMutability; +} + +pub trait GetBorrowChecker { + fn borrow_checker(cell: &PyCell) -> &::Checker; +} + +impl> GetBorrowChecker for MutableClass { + fn borrow_checker(cell: &PyCell) -> &BorrowChecker { + &cell.contents.borrow_checker + } +} + +impl> GetBorrowChecker for ImmutableClass { + fn borrow_checker(cell: &PyCell) -> &EmptySlot { + &cell.contents.borrow_checker + } +} + +impl, M: PyClassMutability> GetBorrowChecker + for ExtendsMutableAncestor +where + T::BaseType: PyClassImpl> + + PyClassBaseType>, + ::PyClassMutability: PyClassMutability, +{ + fn borrow_checker(cell: &PyCell) -> &BorrowChecker { + <::PyClassMutability as GetBorrowChecker>::borrow_checker(&cell.ob_base) + } +} + +pub struct ImmutableClass(()); +pub struct MutableClass(()); +pub struct ExtendsMutableAncestor(PhantomData); + +impl PyClassMutability for ImmutableClass { + type Storage = EmptySlot; + type Checker = EmptySlot; + type ImmutableChild = ImmutableClass; + type MutableChild = MutableClass; +} + +impl PyClassMutability for MutableClass { + type Storage = BorrowChecker; + type Checker = BorrowChecker; + type ImmutableChild = ExtendsMutableAncestor; + type MutableChild = ExtendsMutableAncestor; +} + +impl PyClassMutability for ExtendsMutableAncestor { + type Storage = EmptySlot; + type Checker = BorrowChecker; + type ImmutableChild = ExtendsMutableAncestor; + type MutableChild = ExtendsMutableAncestor; +} + +pub trait Mutability {} + +pub struct Mutable; +impl Mutability for Mutable {} +pub struct Immutable; +impl Mutability for Immutable {} + /// Base layout of PyCell. -/// This is necessary for sharing BorrowFlag between parents and children. #[doc(hidden)] #[repr(C)] pub struct PyCellBase { ob_base: T, - borrow_flag: Cell, } unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} @@ -237,14 +404,15 @@ unsafe impl PyLayout for PyCellBase where U: PySizedLayout {} /// For more information on how, when and why (not) to use `PyCell` please see the /// [module-level documentation](self). #[repr(C)] -pub struct PyCell { +pub struct PyCell { ob_base: ::LayoutAsBase, contents: PyCellContents, } #[repr(C)] -pub(crate) struct PyCellContents { +pub(crate) struct PyCellContents { pub(crate) value: ManuallyDrop>, + pub(crate) borrow_checker: ::Storage, pub(crate) thread_checker: T::ThreadChecker, pub(crate) dict: T::Dict, pub(crate) weakref: T::WeakRef, @@ -281,7 +449,10 @@ impl PyCell { /// /// Panics if the value is currently borrowed. For a non-panicking variant, use /// [`try_borrow_mut`](#method.try_borrow_mut). - pub fn borrow_mut(&self) -> PyRefMut<'_, T> { + pub fn borrow_mut(&self) -> PyRefMut<'_, T> + where + T: MutablePyClass, + { self.try_borrow_mut().expect("Already borrowed") } @@ -311,13 +482,10 @@ impl PyCell { /// }); /// ``` pub fn try_borrow(&self) -> Result, PyBorrowError> { - let flag = self.get_borrow_flag(); - if flag == BorrowFlag::HAS_MUTABLE_BORROW { - Err(PyBorrowError { _private: () }) - } else { - self.set_borrow_flag(flag.increment()); - Ok(PyRef { inner: self }) - } + self.ensure_threadsafe(); + self.borrow_checker() + .try_borrow() + .map(|_| PyRef { inner: self }) } /// Mutably borrows the value `T`, returning an error if the value is currently borrowed. @@ -341,13 +509,14 @@ impl PyCell { /// assert!(c.try_borrow_mut().is_ok()); /// }); /// ``` - pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> { - if self.get_borrow_flag() != BorrowFlag::UNUSED { - Err(PyBorrowMutError { _private: () }) - } else { - self.set_borrow_flag(BorrowFlag::HAS_MUTABLE_BORROW); - Ok(PyRefMut { inner: self }) - } + pub fn try_borrow_mut(&self) -> Result, PyBorrowMutError> + where + T: MutablePyClass, + { + self.ensure_threadsafe(); + self.borrow_checker() + .try_borrow_mut() + .map(|_| PyRefMut { inner: self }) } /// Immutably borrows the value `T`, returning an error if the value is @@ -380,11 +549,10 @@ impl PyCell { /// }); /// ``` pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> { - if self.get_borrow_flag() == BorrowFlag::HAS_MUTABLE_BORROW { - Err(PyBorrowError { _private: () }) - } else { - Ok(&*self.contents.value.get()) - } + self.ensure_threadsafe(); + self.borrow_checker() + .try_borrow_unguarded() + .map(|_: ()| &*self.contents.value.get()) } /// Replaces the wrapped value with a new one, returning the old value. @@ -393,7 +561,10 @@ impl PyCell { /// /// Panics if the value is currently borrowed. #[inline] - pub fn replace(&self, t: T) -> T { + pub fn replace(&self, t: T) -> T + where + T: MutablePyClass, + { std::mem::replace(&mut *self.borrow_mut(), t) } @@ -402,7 +573,10 @@ impl PyCell { /// # Panics /// /// Panics if the value is currently borrowed. - pub fn replace_with T>(&self, f: F) -> T { + pub fn replace_with T>(&self, f: F) -> T + where + T: MutablePyClass, + { let mut_borrow = &mut *self.borrow_mut(); let replacement = f(mut_borrow); std::mem::replace(mut_borrow, replacement) @@ -414,7 +588,10 @@ impl PyCell { /// /// Panics if the value in either `PyCell` is currently borrowed. #[inline] - pub fn swap(&self, other: &Self) { + pub fn swap(&self, other: &Self) + where + T: MutablePyClass, + { std::mem::swap(&mut *self.borrow_mut(), &mut *other.borrow_mut()) } @@ -489,7 +666,13 @@ impl PyCell { } } -unsafe impl PyLayout for PyCell {} +impl PyCell { + fn borrow_checker(&self) -> &::Checker { + T::PyClassMutability::borrow_checker(self) + } +} + +unsafe impl PyLayout for PyCell {} impl PySizedLayout for PyCell {} impl AsPyPointer for PyCell { @@ -594,8 +777,8 @@ impl<'p, T: PyClass> PyRef<'p, T> { impl<'p, T, U> AsRef for PyRef<'p, T> where - T: PyClass, - U: PyClass, + T: MutablePyClass, // For now, only mutable classes can be extended + U: MutablePyClass, { fn as_ref(&self) -> &T::BaseType { unsafe { &*self.inner.ob_base.get_ptr() } @@ -604,8 +787,8 @@ where impl<'p, T, U> PyRef<'p, T> where - T: PyClass, - U: PyClass, + T: MutablePyClass, // For now, only mutable classes can be extended + U: MutablePyClass, { /// Gets a `PyRef`. /// @@ -672,8 +855,7 @@ impl<'p, T: PyClass> Deref for PyRef<'p, T> { impl<'p, T: PyClass> Drop for PyRef<'p, T> { fn drop(&mut self) { - let flag = self.inner.get_borrow_flag(); - self.inner.set_borrow_flag(flag.decrement()) + self.inner.borrow_checker().release_borrow() } } @@ -705,11 +887,11 @@ impl fmt::Debug for PyRef<'_, T> { /// A wrapper type for a mutably borrowed value from a[`PyCell`]``. /// /// See the [module-level documentation](self) for more information. -pub struct PyRefMut<'p, T: PyClass> { +pub struct PyRefMut<'p, T: MutablePyClass> { inner: &'p PyCell, } -impl<'p, T: PyClass> PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> PyRefMut<'p, T> { /// Returns a `Python` token that is bound to the lifetime of the `PyRefMut`. pub fn py(&self) -> Python<'_> { unsafe { Python::assume_gil_acquired() } @@ -718,8 +900,8 @@ impl<'p, T: PyClass> PyRefMut<'p, T> { impl<'p, T, U> AsRef for PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { fn as_ref(&self) -> &T::BaseType { unsafe { &*self.inner.ob_base.get_ptr() } @@ -728,8 +910,8 @@ where impl<'p, T, U> AsMut for PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { fn as_mut(&mut self) -> &mut T::BaseType { unsafe { &mut *self.inner.ob_base.get_ptr() } @@ -738,8 +920,8 @@ where impl<'p, T, U> PyRefMut<'p, T> where - T: PyClass, - U: PyClass, + T: PyClass + MutablePyClass, + U: MutablePyClass, { /// Gets a `PyRef`. /// @@ -753,7 +935,7 @@ where } } -impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> Deref for PyRefMut<'p, T> { type Target = T; #[inline] @@ -762,46 +944,46 @@ impl<'p, T: PyClass> Deref for PyRefMut<'p, T> { } } -impl<'p, T: PyClass> DerefMut for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> DerefMut for PyRefMut<'p, T> { #[inline] fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.inner.get_ptr() } } } -impl<'p, T: PyClass> Drop for PyRefMut<'p, T> { +impl<'p, T: MutablePyClass> Drop for PyRefMut<'p, T> { fn drop(&mut self) { - self.inner.set_borrow_flag(BorrowFlag::UNUSED) + self.inner.borrow_checker().release_borrow_mut() } } -impl IntoPy for PyRefMut<'_, T> { +impl IntoPy for PyRefMut<'_, T> { fn into_py(self, py: Python<'_>) -> PyObject { unsafe { PyObject::from_borrowed_ptr(py, self.inner.as_ptr()) } } } -impl<'a, T: PyClass> AsPyPointer for PyRefMut<'a, T> { +impl<'a, T: MutablePyClass> AsPyPointer for PyRefMut<'a, T> { fn as_ptr(&self) -> *mut ffi::PyObject { self.inner.as_ptr() } } -impl<'a, T: PyClass> std::convert::TryFrom<&'a PyCell> for crate::PyRefMut<'a, T> { +impl<'a, T: MutablePyClass> std::convert::TryFrom<&'a PyCell> for crate::PyRefMut<'a, T> { type Error = PyBorrowMutError; fn try_from(cell: &'a crate::PyCell) -> Result { cell.try_borrow_mut() } } -impl fmt::Debug for PyRefMut<'_, T> { +impl fmt::Debug for PyRefMut<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&*(self.deref()), f) } } #[doc(hidden)] -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct BorrowFlag(usize); impl BorrowFlag { @@ -867,8 +1049,7 @@ impl From for PyErr { #[doc(hidden)] pub trait PyCellLayout: PyLayout { - fn get_borrow_flag(&self) -> BorrowFlag; - fn set_borrow_flag(&self, flag: BorrowFlag); + fn ensure_threadsafe(&self); /// Implementation of tp_dealloc. /// # Safety /// - slf must be a valid pointer to an instance of a T or a subclass. @@ -881,12 +1062,7 @@ where U: PySizedLayout, T: PyTypeInfo, { - fn get_borrow_flag(&self) -> BorrowFlag { - self.borrow_flag.get() - } - fn set_borrow_flag(&self, flag: BorrowFlag) { - self.borrow_flag.set(flag) - } + fn ensure_threadsafe(&self) {} unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>) { // For `#[pyclass]` types which inherit from PyAny, we can just call tp_free if T::type_object_raw(py) == &mut PyBaseObject_Type { @@ -908,16 +1084,13 @@ where } } -impl PyCellLayout for PyCell +impl PyCellLayout for PyCell where ::LayoutAsBase: PyCellLayout, { - fn get_borrow_flag(&self) -> BorrowFlag { + fn ensure_threadsafe(&self) { self.contents.thread_checker.ensure(); - self.ob_base.get_borrow_flag() - } - fn set_borrow_flag(&self, flag: BorrowFlag) { - self.ob_base.set_borrow_flag(flag) + self.ob_base.ensure_threadsafe(); } unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>) { // Safety: Python only calls tp_dealloc when no references to the object remain. diff --git a/src/pyclass.rs b/src/pyclass.rs index eb2bb5c9595..7cdbd8e9507 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,14 +1,14 @@ //! `PyClass` and related traits. +use crate::pycell::{Immutable, Mutable}; use crate::{ callback::IntoPyCallbackOutput, exceptions::PyTypeError, ffi, impl_::pyclass::{ - assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc, PyClassDict, - PyClassImpl, PyClassItems, PyClassWeakRef, + assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc, PyClassImpl, + PyClassItems, }, - IntoPy, IntoPyPointer, PyCell, PyErr, PyMethodDefType, PyNativeType, PyObject, PyResult, - PyTypeInfo, Python, + IntoPy, IntoPyPointer, PyCell, PyErr, PyMethodDefType, PyObject, PyResult, PyTypeInfo, Python, }; use std::{ convert::TryInto, @@ -25,15 +25,14 @@ use std::{ pub trait PyClass: PyTypeInfo> + PyClassImpl> { - /// Specify this class has `#[pyclass(dict)]` or not. - type Dict: PyClassDict; - /// Specify this class has `#[pyclass(weakref)]` or not. - type WeakRef: PyClassWeakRef; - /// The closest native ancestor. This is `PyAny` by default, and when you declare - /// `#[pyclass(extends=PyDict)]`, it's `PyDict`. - type BaseNativeType: PyTypeInfo + PyNativeType; } +pub trait MutablePyClass: PyClass {} +pub trait ImmutablePyClass: PyClass {} + +impl MutablePyClass for T where T: PyClass {} +impl ImmutablePyClass for T where T: PyClass {} + fn into_raw(vec: Vec) -> *mut c_void { Box::into_raw(vec.into_boxed_slice()) as _ } diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index f7b63fe5278..273e6b3f996 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -1,14 +1,15 @@ //! Contains initialization utilities for `#[pyclass]`. use crate::callback::IntoPyCallbackOutput; use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef}; +use crate::pyclass::MutablePyClass; use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python}; use crate::{ ffi::PyTypeObject, - pycell::{BorrowFlag, PyCellContents}, + pycell::{PyCellContents, PyClassBorrowChecker, PyClassMutability}, type_object::{get_tp_alloc, PyTypeInfo}, }; use std::{ - cell::{Cell, UnsafeCell}, + cell::UnsafeCell, marker::PhantomData, mem::{ManuallyDrop, MaybeUninit}, }; @@ -81,7 +82,7 @@ impl PyObjectInit for PyNativeTypeInitializer { /// Initializer for our `#[pyclass]` system. /// -/// You can use this type to initalize complicatedly nested `#[pyclass]`. +/// You can use this type to initialize complicatedly nested `#[pyclass]`. /// /// # Examples /// @@ -228,14 +229,6 @@ impl PyObjectInit for PyClassInitializer { py: Python<'_>, subtype: *mut PyTypeObject, ) -> PyResult<*mut ffi::PyObject> { - /// Layout of a PyCellBase after base new has been called, but the borrow flag has not - /// yet been initialized. - #[repr(C)] - struct PartiallyInitializedPyCellBase { - _ob_base: T, - borrow_flag: MaybeUninit>, - } - /// Layout of a PyCell after base new has been called, but the contents have not yet been /// written. #[repr(C)] @@ -247,19 +240,12 @@ impl PyObjectInit for PyClassInitializer { let Self { init, super_init } = self; let obj = super_init.into_new_object(py, subtype)?; - // FIXME: Only need to initialize borrow flag once per whole hierarchy - let base: *mut PartiallyInitializedPyCellBase = obj as _; - std::ptr::write( - (*base).borrow_flag.as_mut_ptr(), - Cell::new(BorrowFlag::UNUSED), - ); - - // FIXME: Initialize borrow flag if necessary?? let cell: *mut PartiallyInitializedPyCell = obj as _; std::ptr::write( (*cell).contents.as_mut_ptr(), PyCellContents { value: ManuallyDrop::new(UnsafeCell::new(init)), + borrow_checker: ::Storage::new(), thread_checker: T::ThreadChecker::new(), dict: T::Dict::new(), weakref: T::WeakRef::new(), @@ -284,8 +270,8 @@ where impl From<(S, B)> for PyClassInitializer where - S: PyClass, - B: PyClass, + S: MutablePyClass, + B: MutablePyClass, B::BaseType: PyClassBaseType>, { fn from(sub_and_base: (S, B)) -> PyClassInitializer { diff --git a/src/type_object.rs b/src/type_object.rs index b8c2d654133..5f800e8eb06 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -13,7 +13,7 @@ use parking_lot::{const_mutex, Mutex}; use std::thread::{self, ThreadId}; /// `T: PyLayout` represents that `T` is a concrete representation of `U` in the Python heap. -/// E.g., `PyCell` is a concrete representaion of all `pyclass`es, and `ffi::PyObject` +/// E.g., `PyCell` is a concrete representation of all `pyclass`es, and `ffi::PyObject` /// is of `PyAny`. /// /// This trait is intended to be used internally. @@ -25,7 +25,7 @@ pub unsafe trait PyLayout {} /// `T: PySizedLayout` represents that `T` is not a instance of /// [`PyVarObject`](https://docs.python.org/3.8/c-api/structures.html?highlight=pyvarobject#c.PyVarObject). -/// In addition, that `T` is a concrete representaion of `U`. +/// In addition, that `T` is a concrete representation of `U`. pub trait PySizedLayout: PyLayout + Sized {} /// Python type information. @@ -51,9 +51,14 @@ pub unsafe trait PyTypeInfo: Sized { /// Utility type to make Py::as_ref work. type AsRefTarget: PyNativeType; - /// PyTypeObject instance for this type. + /// Returns the PyTypeObject instance for this type. fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject; + /// Returns the safe abstraction over the type object. + fn type_object(py: Python<'_>) -> &PyType { + unsafe { py.from_borrowed_ptr(Self::type_object_raw(py) as _) } + } + /// Checks if `object` is an instance of this type or a subclass of this type. fn is_type_of(object: &PyAny) -> bool { unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 } @@ -65,27 +70,19 @@ pub unsafe trait PyTypeInfo: Sized { } } -/// Python object types that have a corresponding type object. +/// Legacy trait which previously held the `type_object` method now found on `PyTypeInfo`. /// /// # Safety /// -/// This trait is marked unsafe because not fulfilling the contract for type_object -/// leads to UB. -/// -/// See also [PyTypeInfo::type_object_raw](trait.PyTypeInfo.html#tymethod.type_object_raw). -pub unsafe trait PyTypeObject { - /// Returns the safe abstraction over the type object. - fn type_object(py: Python<'_>) -> &PyType; -} +/// This trait used to have stringent safety requirements, but they are now irrelevant as it is deprecated. +#[deprecated( + since = "0.17.0", + note = "PyTypeObject::type_object was moved to PyTypeInfo::type_object" +)] +pub unsafe trait PyTypeObject: PyTypeInfo {} -unsafe impl PyTypeObject for T -where - T: PyTypeInfo, -{ - fn type_object(py: Python<'_>) -> &PyType { - unsafe { py.from_borrowed_ptr(Self::type_object_raw(py) as _) } - } -} +#[allow(deprecated)] +unsafe impl PyTypeObject for T {} /// Lazy type object for PyClass. #[doc(hidden)] @@ -182,7 +179,7 @@ impl LazyStaticType { if let Err(err) = result { err.clone_ref(py).print(py); - panic!("An error occured while initializing `{}.__dict__`", name); + panic!("An error occurred while initializing `{}.__dict__`", name); } } } @@ -232,3 +229,23 @@ pub(crate) unsafe fn get_tp_free(tp: *mut ffi::PyTypeObject) -> ffi::freefunc { std::mem::transmute(ptr) } } + +#[cfg(test)] +mod tests { + #[test] + #[allow(deprecated)] + fn test_deprecated_type_object() { + // Even though PyTypeObject is deprecated, simple usages of it as a trait bound should continue to work. + use super::PyTypeObject; + use crate::types::{PyList, PyType}; + use crate::Python; + + fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) + } + + Python::with_gil(|py| { + assert!(get_type_object::(py).is(::type_object(py))) + }); + } +} diff --git a/src/types/any.rs b/src/types/any.rs index 9ba9b515944..26dd5c679e2 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,10 +1,8 @@ use crate::class::basic::CompareOp; -use crate::conversion::{ - AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject, -}; +use crate::conversion::{AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToPyObject}; use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::exceptions::PyTypeError; -use crate::type_object::PyTypeObject; +use crate::type_object::PyTypeInfo; use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; use crate::{err, ffi, Py, PyNativeType, PyObject, Python}; use std::cell::UnsafeCell; @@ -103,9 +101,9 @@ impl PyAny { where N: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name) != 0) - }) + unsafe { + Ok(ffi::PyObject_HasAttr(self.as_ptr(), attr_name.to_object(self.py()).as_ptr()) != 0) + } } /// Retrieves an attribute value. @@ -134,10 +132,12 @@ impl PyAny { where N: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_GetAttr(self.as_ptr(), attr_name)) - }) + unsafe { + self.py().from_owned_ptr_or_err(ffi::PyObject_GetAttr( + self.as_ptr(), + attr_name.to_object(self.py()).as_ptr(), + )) + } } /// Sets an attribute value. @@ -164,17 +164,20 @@ impl PyAny { /// ``` pub fn setattr(&self, attr_name: N, value: V) -> PyResult<()> where - N: ToBorrowedObject, - V: ToBorrowedObject, + N: ToPyObject, + V: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), move |attr_name| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone( - self.py(), - ffi::PyObject_SetAttr(self.as_ptr(), attr_name, value), - ) - }) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyObject_SetAttr( + self.as_ptr(), + attr_name.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Deletes an attribute. @@ -184,9 +187,13 @@ impl PyAny { where N: ToPyObject, { - attr_name.with_borrowed_ptr(self.py(), |attr_name| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_DelAttr(self.as_ptr(), attr_name)) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + self.py(), + ffi::PyObject_DelAttr(self.as_ptr(), attr_name.to_object(py).as_ptr()), + ) + } } /// Returns an [`Ordering`] between `self` and `other`. @@ -239,26 +246,29 @@ impl PyAny { where O: ToPyObject, { + self._compare(other.to_object(self.py())) + } + + fn _compare(&self, other: PyObject) -> PyResult { let py = self.py(); + let other = other.as_ptr(); // Almost the same as ffi::PyObject_RichCompareBool, but this one doesn't try self == other. // See https://github.com/PyO3/pyo3/issues/985 for more. let do_compare = |other, op| unsafe { PyObject::from_owned_ptr_or_err(py, ffi::PyObject_RichCompare(self.as_ptr(), other, op)) .and_then(|obj| obj.is_true(py)) }; - other.with_borrowed_ptr(py, |other| { - if do_compare(other, ffi::Py_EQ)? { - Ok(Ordering::Equal) - } else if do_compare(other, ffi::Py_LT)? { - Ok(Ordering::Less) - } else if do_compare(other, ffi::Py_GT)? { - Ok(Ordering::Greater) - } else { - Err(PyTypeError::new_err( - "PyAny::compare(): All comparisons returned false", - )) - } - }) + if do_compare(other, ffi::Py_EQ)? { + Ok(Ordering::Equal) + } else if do_compare(other, ffi::Py_LT)? { + Ok(Ordering::Less) + } else if do_compare(other, ffi::Py_GT)? { + Ok(Ordering::Greater) + } else { + Err(PyTypeError::new_err( + "PyAny::compare(): All comparisons returned false", + )) + } } /// Tests whether two Python objects obey a given [`CompareOp`]. @@ -296,13 +306,11 @@ impl PyAny { O: ToPyObject, { unsafe { - other.with_borrowed_ptr(self.py(), |other| { - self.py().from_owned_ptr_or_err(ffi::PyObject_RichCompare( - self.as_ptr(), - other, - compare_op as c_int, - )) - }) + self.py().from_owned_ptr_or_err(ffi::PyObject_RichCompare( + self.as_ptr(), + other.to_object(self.py()).as_ptr(), + compare_op as c_int, + )) } } @@ -519,9 +527,9 @@ impl PyAny { args: impl IntoPy>, kwargs: Option<&PyDict>, ) -> PyResult<&PyAny> { - name.with_borrowed_ptr(self.py(), |name| unsafe { + unsafe { let py = self.py(); - let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name); + let ptr = ffi::PyObject_GetAttr(self.as_ptr(), name.to_object(py).as_ptr()); if ptr.is_null() { return Err(PyErr::fetch(py)); } @@ -533,7 +541,7 @@ impl PyAny { ffi::Py_XDECREF(args); ffi::Py_XDECREF(kwargs); result - }) + } } /// Calls a method on the object without arguments. @@ -639,12 +647,14 @@ impl PyAny { /// This is equivalent to the Python expression `self[key]`. pub fn get_item(&self, key: K) -> PyResult<&PyAny> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - self.py() - .from_owned_ptr_or_err(ffi::PyObject_GetItem(self.as_ptr(), key)) - }) + unsafe { + self.py().from_owned_ptr_or_err(ffi::PyObject_GetItem( + self.as_ptr(), + key.to_object(self.py()).as_ptr(), + )) + } } /// Sets a collection item value. @@ -652,14 +662,20 @@ impl PyAny { /// This is equivalent to the Python expression `self[key] = value`. pub fn set_item(&self, key: K, value: V) -> PyResult<()> where - K: ToBorrowedObject, - V: ToBorrowedObject, + K: ToPyObject, + V: ToPyObject, { - key.with_borrowed_ptr(self.py(), move |key| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_SetItem(self.as_ptr(), key, value)) - }) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyObject_SetItem( + self.as_ptr(), + key.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Deletes an item from the collection. @@ -667,11 +683,14 @@ impl PyAny { /// This is equivalent to the Python expression `del self[key]`. pub fn del_item(&self, key: K) -> PyResult<()> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - err::error_on_minusone(self.py(), ffi::PyObject_DelItem(self.as_ptr(), key)) - }) + unsafe { + err::error_on_minusone( + self.py(), + ffi::PyObject_DelItem(self.as_ptr(), key.to_object(self.py()).as_ptr()), + ) + } } /// Takes an object and returns an iterator for it. @@ -782,22 +801,22 @@ impl PyAny { /// /// This is equivalent to the Python expression `isinstance(self, T)`, /// if the type `T` is known at compile time. - pub fn is_instance_of(&self) -> PyResult { + pub fn is_instance_of(&self) -> PyResult { self.is_instance(T::type_object(self.py())) } /// Determines if self contains `value`. /// /// This is equivalent to the Python expression `value in self`. - #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Contains(self.as_ptr(), ptr) - }); - match r { + self._contains(value.to_object(self.py())) + } + + fn _contains(&self, value: PyObject) -> PyResult { + match unsafe { ffi::PySequence_Contains(self.as_ptr(), value.as_ptr()) } { 0 => Ok(false), 1 => Ok(true), _ => Err(PyErr::fetch(self.py())), @@ -814,7 +833,7 @@ impl PyAny { #[cfg(test)] mod tests { use crate::{ - type_object::PyTypeObject, + type_object::PyTypeInfo, types::{IntoPyDict, PyList, PyLong, PyModule}, Python, ToPyObject, }; diff --git a/src/types/cls.rs b/src/types/cls.rs deleted file mode 100644 index 7b1534287ed..00000000000 --- a/src/types/cls.rs +++ /dev/null @@ -1,584 +0,0 @@ -// Copyright (c) 2017-present PyO3 Project and Contributors - -//! Defines conversions between Rust and Python types. -use crate::err::{self, PyDowncastError, PyResult}; -use crate::type_object::PyTypeInfo; -use crate::types::PyTuple; -use crate::{ - ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python, -}; -use std::ptr::NonNull; - -/// This trait represents that **we can do zero-cost conversion from the object -/// to a FFI pointer**. -/// -/// This trait is implemented for types that internally wrap a pointer to a Python object. -/// -/// # Examples -/// -/// ``` -/// use pyo3::{prelude::*, AsPyPointer}; -/// Python::with_gil(|py| { -/// let dict = pyo3::types::PyDict::new(py); -/// // All native object wrappers implement AsPyPointer!!! -/// assert_ne!(dict.as_ptr(), std::ptr::null_mut()); -/// }); -/// ``` -pub trait AsPyPointer { - /// Retrieves the underlying FFI pointer (as a borrowed pointer). - fn as_ptr(&self) -> *mut ffi::PyObject; -} - -/// This trait allows retrieving the underlying FFI pointer from Python objects. -pub trait IntoPyPointer { - /// Retrieves the underlying FFI pointer. Whether pointer owned or borrowed - /// depends on implementation. - fn into_ptr(self) -> *mut ffi::PyObject; -} - -/// Convert `None` into a null pointer. -impl AsPyPointer for Option -where - T: AsPyPointer, -{ - #[inline] - fn as_ptr(&self) -> *mut ffi::PyObject { - self.as_ref() - .map_or_else(std::ptr::null_mut, |t| t.as_ptr()) - } -} - -/// Convert `None` into a null pointer. -impl IntoPyPointer for Option -where - T: IntoPyPointer, -{ - #[inline] - fn into_ptr(self) -> *mut ffi::PyObject { - self.map_or_else(std::ptr::null_mut, |t| t.into_ptr()) - } -} - -impl<'a, T> IntoPyPointer for &'a T -where - T: AsPyPointer, -{ - fn into_ptr(self) -> *mut ffi::PyObject { - unsafe { ffi::_Py_XNewRef(self.as_ptr()) } - } -} - -/// Conversion trait that allows various objects to be converted into `PyObject`. -pub trait ToPyObject { - /// Converts self into a Python object. - fn to_object(&self, py: Python<'_>) -> PyObject; -} - -/// This trait has two implementations: The slow one is implemented for -/// all [ToPyObject] and creates a new object using [ToPyObject::to_object], -/// while the fast one is only implemented for AsPyPointer (we know -/// that every AsPyPointer is also ToPyObject) and uses [AsPyPointer::as_ptr()] -pub trait ToBorrowedObject: ToPyObject { - /// Converts self into a Python object and calls the specified closure - /// on the native FFI pointer underlying the Python object. - /// - /// May be more efficient than `to_object` because it does not need - /// to touch any reference counts when the input object already is a Python object. - fn with_borrowed_ptr(&self, py: Python<'_>, f: F) -> R - where - F: FnOnce(*mut ffi::PyObject) -> R, - { - let ptr = self.to_object(py).into_ptr(); - let result = f(ptr); - unsafe { - ffi::Py_XDECREF(ptr); - } - result - } -} - -impl ToBorrowedObject for T where T: ToPyObject {} - -/// Defines a conversion from a Rust type to a Python object. -/// -/// It functions similarly to std's [`Into`](std::convert::Into) trait, -/// but requires a [GIL token](Python) as an argument. -/// Many functions and traits internal to PyO3 require this trait as a bound, -/// so a lack of this trait can manifest itself in different error messages. -/// -/// # Examples -/// ## With `#[pyclass]` -/// The easiest way to implement `IntoPy` is by exposing a struct as a native Python object -/// by annotating it with [`#[pyclass]`](crate::prelude::pyclass). -/// -/// ```rust -/// use pyo3::prelude::*; -/// -/// #[pyclass] -/// struct Number { -/// #[pyo3(get, set)] -/// value: i32, -/// } -/// ``` -/// Python code will see this as an instance of the `Number` class with a `value` attribute. -/// -/// ## Conversion to a Python object -/// -/// However, it may not be desirable to expose the existence of `Number` to Python code. -/// `IntoPy` allows us to define a conversion to an appropriate Python object. -/// ```rust -/// use pyo3::prelude::*; -/// -/// struct Number { -/// value: i32, -/// } -/// -/// impl IntoPy for Number { -/// fn into_py(self, py: Python<'_>) -> PyObject { -/// // delegates to i32's IntoPy implementation. -/// self.value.into_py(py) -/// } -/// } -/// ``` -/// Python code will see this as an `int` object. -/// -/// ## Dynamic conversion into Python objects. -/// It is also possible to return a different Python object depending on some condition. -/// This is useful for types like enums that can carry different types. -/// -/// ```rust -/// use pyo3::prelude::*; -/// -/// enum Value { -/// Integer(i32), -/// String(String), -/// None, -/// } -/// -/// impl IntoPy for Value { -/// fn into_py(self, py: Python<'_>) -> PyObject { -/// match self { -/// Self::Integer(val) => val.into_py(py), -/// Self::String(val) => val.into_py(py), -/// Self::None => py.None(), -/// } -/// } -/// } -/// # fn main() { -/// # Python::with_gil(|py| { -/// # let v = Value::Integer(73).into_py(py); -/// # let v = v.extract::(py).unwrap(); -/// # -/// # let v = Value::String("foo".into()).into_py(py); -/// # let v = v.extract::(py).unwrap(); -/// # -/// # let v = Value::None.into_py(py); -/// # let v = v.extract::>>(py).unwrap(); -/// # }); -/// # } -/// ``` -/// Python code will see this as any of the `int`, `string` or `None` objects. -#[cfg_attr(docsrs, doc(alias = "IntoPyCallbackOutput"))] -pub trait IntoPy: Sized { - /// Performs the conversion. - fn into_py(self, py: Python<'_>) -> T; -} - -/// `FromPyObject` is implemented by various types that can be extracted from -/// a Python object reference. -/// -/// Normal usage is through the helper methods `Py::extract` or `PyAny::extract`: -/// -/// ```rust,ignore -/// let obj: Py = ...; -/// let value: &TargetType = obj.extract(py)?; -/// -/// let any: &PyAny = ...; -/// let value: &TargetType = any.extract()?; -/// ``` -/// -/// Note: depending on the implementation, the lifetime of the extracted result may -/// depend on the lifetime of the `obj` or the `prepared` variable. -/// -/// For example, when extracting `&str` from a Python byte string, the resulting string slice will -/// point to the existing string data (lifetime: `'source`). -/// On the other hand, when extracting `&str` from a Python Unicode string, the preparation step -/// will convert the string to UTF-8, and the resulting string slice will have lifetime `'prepared`. -/// Since which case applies depends on the runtime type of the Python object, -/// both the `obj` and `prepared` variables must outlive the resulting string slice. -/// -/// The trait's conversion method takes a `&PyAny` argument but is called -/// `FromPyObject` for historical reasons. -pub trait FromPyObject<'source>: Sized { - /// Extracts `Self` from the source `PyObject`. - fn extract(ob: &'source PyAny) -> PyResult; -} - -/// Identity conversion: allows using existing `PyObject` instances where -/// `T: ToPyObject` is expected. -impl ToPyObject for &'_ T { - #[inline] - fn to_object(&self, py: Python<'_>) -> PyObject { - ::to_object(*self, py) - } -} - -/// `Option::Some` is converted like `T`. -/// `Option::None` is converted to Python `None`. -impl ToPyObject for Option -where - T: ToPyObject, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - self.as_ref() - .map_or_else(|| py.None(), |val| val.to_object(py)) - } -} - -impl IntoPy for Option -where - T: IntoPy, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - self.map_or_else(|| py.None(), |val| val.into_py(py)) - } -} - -/// `()` is converted to Python `None`. -impl ToPyObject for () { - fn to_object(&self, py: Python<'_>) -> PyObject { - py.None() - } -} - -impl IntoPy for () { - fn into_py(self, py: Python<'_>) -> PyObject { - py.None() - } -} - -impl IntoPy for &'_ T -where - T: AsPyPointer, -{ - #[inline] - fn into_py(self, py: Python<'_>) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -impl<'a, T> FromPyObject<'a> for &'a PyCell -where - T: PyClass, -{ - fn extract(obj: &'a PyAny) -> PyResult { - PyTryFrom::try_from(obj).map_err(Into::into) - } -} - -impl<'a, T> FromPyObject<'a> for T -where - T: PyClass + Clone, -{ - fn extract(obj: &'a PyAny) -> PyResult { - let cell: &PyCell = PyTryFrom::try_from(obj)?; - Ok(unsafe { cell.try_borrow_unguarded()?.clone() }) - } -} - -impl<'a, T> FromPyObject<'a> for PyRef<'a, T> -where - T: PyClass, -{ - fn extract(obj: &'a PyAny) -> PyResult { - let cell: &PyCell = PyTryFrom::try_from(obj)?; - cell.try_borrow().map_err(Into::into) - } -} - -impl<'a, T> FromPyObject<'a> for PyRefMut<'a, T> -where - T: PyClass, -{ - fn extract(obj: &'a PyAny) -> PyResult { - let cell: &PyCell = PyTryFrom::try_from(obj)?; - cell.try_borrow_mut().map_err(Into::into) - } -} - -impl<'a, T> FromPyObject<'a> for Option -where - T: FromPyObject<'a>, -{ - fn extract(obj: &'a PyAny) -> PyResult { - if obj.as_ptr() == unsafe { ffi::Py_None() } { - Ok(None) - } else { - T::extract(obj).map(Some) - } - } -} - -/// Trait implemented by Python object types that allow a checked downcast. -/// If `T` implements `PyTryFrom`, we can convert `&PyAny` to `&T`. -/// -/// This trait is similar to `std::convert::TryFrom` -pub trait PyTryFrom<'v>: Sized + PyNativeType { - /// Cast from a concrete Python object type to PyObject. - fn try_from>(value: V) -> Result<&'v Self, PyDowncastError<'v>>; - - /// Cast from a concrete Python object type to PyObject. With exact type check. - fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError<'v>>; - - /// Cast a PyAny to a specific type of PyObject. The caller must - /// have already verified the reference is for this type. - /// - /// # Safety - /// - /// Callers must ensure that the type is valid or risk type confusion. - unsafe fn try_from_unchecked>(value: V) -> &'v Self; -} - -/// Trait implemented by Python object types that allow a checked downcast. -/// This trait is similar to `std::convert::TryInto` -pub trait PyTryInto: Sized { - /// Cast from PyObject to a concrete Python object type. - fn try_into(&self) -> Result<&T, PyDowncastError<'_>>; - - /// Cast from PyObject to a concrete Python object type. With exact type check. - fn try_into_exact(&self) -> Result<&T, PyDowncastError<'_>>; -} - -// TryFrom implies TryInto -impl PyTryInto for PyAny -where - U: for<'v> PyTryFrom<'v>, -{ - fn try_into(&self) -> Result<&U, PyDowncastError<'_>> { - >::try_from(self) - } - fn try_into_exact(&self) -> Result<&U, PyDowncastError<'_>> { - U::try_from_exact(self) - } -} - -impl<'v, T> PyTryFrom<'v> for T -where - T: PyTypeInfo + PyNativeType, -{ - fn try_from>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - - fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_exact_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - - #[inline] - unsafe fn try_from_unchecked>(value: V) -> &'v Self { - Self::unchecked_downcast(value.into()) - } -} - -impl<'v, T> PyTryFrom<'v> for PyCell -where - T: 'v + PyClass, -{ - fn try_from>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - fn try_from_exact>(value: V) -> Result<&'v Self, PyDowncastError<'v>> { - let value = value.into(); - unsafe { - if T::is_exact_type_of(value) { - Ok(Self::try_from_unchecked(value)) - } else { - Err(PyDowncastError::new(value, T::NAME)) - } - } - } - #[inline] - unsafe fn try_from_unchecked>(value: V) -> &'v Self { - Self::unchecked_downcast(value.into()) - } -} - -/// Converts `()` to an empty Python tuple. -impl IntoPy> for () { - fn into_py(self, py: Python<'_>) -> Py { - PyTuple::empty(py).into() - } -} - -/// Raw level conversion between `*mut ffi::PyObject` and PyO3 types. -/// -/// # Safety -/// -/// See safety notes on individual functions. -pub unsafe trait FromPyPointer<'p>: Sized { - /// Convert from an arbitrary `PyObject`. - /// - /// # Safety - /// - /// Implementations must ensure the object does not get freed during `'p` - /// and ensure that `ptr` is of the correct type. - /// Note that it must be safe to decrement the reference count of `ptr`. - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self>; - /// Convert from an arbitrary `PyObject` or panic. - /// - /// # Safety - /// - /// Relies on [`from_owned_ptr_or_opt`](#method.from_owned_ptr_or_opt). - unsafe fn from_owned_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_owned_ptr_or_opt(py, ptr).unwrap_or_else(|| err::panic_after_error(py)) - } - /// Convert from an arbitrary `PyObject` or panic. - /// - /// # Safety - /// - /// Relies on [`from_owned_ptr_or_opt`](#method.from_owned_ptr_or_opt). - unsafe fn from_owned_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_owned_ptr_or_panic(py, ptr) - } - /// Convert from an arbitrary `PyObject`. - /// - /// # Safety - /// - /// Relies on [`from_owned_ptr_or_opt`](#method.from_owned_ptr_or_opt). - unsafe fn from_owned_ptr_or_err(py: Python<'p>, ptr: *mut ffi::PyObject) -> PyResult<&'p Self> { - Self::from_owned_ptr_or_opt(py, ptr).ok_or_else(|| err::PyErr::fetch(py)) - } - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Implementations must ensure the object does not get freed during `'p` and avoid type confusion. - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) - -> Option<&'p Self>; - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Relies on unsafe fn [`from_borrowed_ptr_or_opt`](#method.from_borrowed_ptr_or_opt). - unsafe fn from_borrowed_ptr_or_panic(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_borrowed_ptr_or_opt(py, ptr).unwrap_or_else(|| err::panic_after_error(py)) - } - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Relies on unsafe fn [`from_borrowed_ptr_or_opt`](#method.from_borrowed_ptr_or_opt). - unsafe fn from_borrowed_ptr(py: Python<'p>, ptr: *mut ffi::PyObject) -> &'p Self { - Self::from_borrowed_ptr_or_panic(py, ptr) - } - /// Convert from an arbitrary borrowed `PyObject`. - /// - /// # Safety - /// - /// Relies on unsafe fn [`from_borrowed_ptr_or_opt`](#method.from_borrowed_ptr_or_opt). - unsafe fn from_borrowed_ptr_or_err( - py: Python<'p>, - ptr: *mut ffi::PyObject, - ) -> PyResult<&'p Self> { - Self::from_borrowed_ptr_or_opt(py, ptr).ok_or_else(|| err::PyErr::fetch(py)) - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for T -where - T: 'p + crate::PyNativeType, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option<&'p Self> { - gil::register_owned(py, NonNull::new(ptr)?); - Some(&*(ptr as *mut Self)) - } - unsafe fn from_borrowed_ptr_or_opt( - _py: Python<'p>, - ptr: *mut ffi::PyObject, - ) -> Option<&'p Self> { - NonNull::new(ptr as *mut Self).map(|p| &*p.as_ptr()) - } -} - -#[cfg(test)] -mod tests { - use crate::types::{IntoPyDict, PyAny, PyDict, PyList}; - use crate::{AsPyPointer, PyObject, Python, ToPyObject}; - - use super::PyTryFrom; - - #[test] - fn test_try_from() { - Python::with_gil(|py| { - let list: &PyAny = vec![3, 6, 5, 4, 7].to_object(py).into_ref(py); - let dict: &PyAny = vec![("reverse", true)].into_py_dict(py).as_ref(); - - assert!(>::try_from(list).is_ok()); - assert!(>::try_from(dict).is_ok()); - - assert!(>::try_from(list).is_ok()); - assert!(>::try_from(dict).is_ok()); - }); - } - - #[test] - fn test_try_from_exact() { - Python::with_gil(|py| { - let list: &PyAny = vec![3, 6, 5, 4, 7].to_object(py).into_ref(py); - let dict: &PyAny = vec![("reverse", true)].into_py_dict(py).as_ref(); - - assert!(PyList::try_from_exact(list).is_ok()); - assert!(PyDict::try_from_exact(dict).is_ok()); - - assert!(PyAny::try_from_exact(list).is_err()); - assert!(PyAny::try_from_exact(dict).is_err()); - }); - } - - #[test] - fn test_try_from_unchecked() { - Python::with_gil(|py| { - let list = PyList::new(py, &[1, 2, 3]); - let val = unsafe { ::try_from_unchecked(list.as_ref()) }; - assert!(list.is(val)); - }); - } - - #[test] - fn test_option_as_ptr() { - Python::with_gil(|py| { - let mut option: Option = None; - assert_eq!(option.as_ptr(), std::ptr::null_mut()); - - let none = py.None(); - option = Some(none.clone()); - - let ref_cnt = none.get_refcnt(py); - assert_eq!(option.as_ptr(), none.as_ptr()); - - // Ensure ref count not changed by as_ptr call - assert_eq!(none.get_refcnt(py), ref_cnt); - }); - } -} diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 907827744c5..67cdf22c3ea 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -41,7 +41,7 @@ fn ensure_datetime_api(_py: Python<'_>) -> &'static PyDateTime_CAPI { // These are bindings around the C API typecheck macros, all of them return // `1` if True and `0` if False. In all type check macros, the argument (`op`) // must not be `NULL`. The implementations here all call ensure_datetime_api -// to ensure that the PyDateTimeAPI is initalized before use +// to ensure that the PyDateTimeAPI is initialized before use // // // # Safety diff --git a/src/types/dict.rs b/src/types/dict.rs index 794fbea7151..f9e4ffb279f 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -4,10 +4,7 @@ use crate::err::{self, PyErr, PyResult}; use crate::types::{PyAny, PyList}; #[cfg(not(PyPy))] use crate::IntoPyPointer; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyObject, PyTryFrom, Python, ToBorrowedObject, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, IntoPy, PyObject, PyTryFrom, Python, ToPyObject}; use std::collections::{BTreeMap, HashMap}; use std::ptr::NonNull; use std::{cmp, collections, hash}; @@ -82,15 +79,15 @@ impl PyDict { /// This is equivalent to the Python expression `key in self`. pub fn contains(&self, key: K) -> PyResult where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - match ffi::PyDict_Contains(self.as_ptr(), key) { + unsafe { + match ffi::PyDict_Contains(self.as_ptr(), key.to_object(self.py()).as_ptr()) { 1 => Ok(true), 0 => Ok(false), _ => Err(PyErr::fetch(self.py())), } - }) + } } /// Gets an item from the dictionary. @@ -100,15 +97,15 @@ impl PyDict { /// To get a `KeyError` for non-existing keys, use `PyAny::get_item`. pub fn get_item(&self, key: K) -> Option<&PyAny> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - let ptr = ffi::PyDict_GetItem(self.as_ptr(), key); + unsafe { + let ptr = ffi::PyDict_GetItem(self.as_ptr(), key.to_object(self.py()).as_ptr()); NonNull::new(ptr).map(|p| { // PyDict_GetItem return s borrowed ptr, must make it owned for safety (see #890). self.py().from_owned_ptr(ffi::_Py_NewRef(p.as_ptr())) }) - }) + } } /// Sets an item value. @@ -119,11 +116,17 @@ impl PyDict { K: ToPyObject, V: ToPyObject, { - key.with_borrowed_ptr(self.py(), move |key| { - value.with_borrowed_ptr(self.py(), |value| unsafe { - err::error_on_minusone(self.py(), ffi::PyDict_SetItem(self.as_ptr(), key, value)) - }) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyDict_SetItem( + self.as_ptr(), + key.to_object(py).as_ptr(), + value.to_object(py).as_ptr(), + ), + ) + } } /// Deletes an item. @@ -131,11 +134,15 @@ impl PyDict { /// This is equivalent to the Python statement `del self[key]`. pub fn del_item(&self, key: K) -> PyResult<()> where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - err::error_on_minusone(self.py(), ffi::PyDict_DelItem(self.as_ptr(), key)) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyDict_DelItem(self.as_ptr(), key.to_object(py).as_ptr()), + ) + } } /// Returns a list of dict keys. diff --git a/src/types/list.rs b/src/types/list.rs index f8da1119f8b..2538833dec7 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -9,8 +9,7 @@ use crate::ffi::{self, Py_ssize_t}; use crate::internal_tricks::get_ssize_index; use crate::types::PySequence; use crate::{ - AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, PyTryFrom, Python, ToBorrowedObject, - ToPyObject, + AsPyPointer, IntoPy, IntoPyPointer, Py, PyAny, PyObject, PyTryFrom, Python, ToPyObject, }; /// Represents a Python `list`. @@ -223,11 +222,15 @@ impl PyList { /// Appends an item to the list. pub fn append(&self, item: I) -> PyResult<()> where - I: ToBorrowedObject, + I: ToPyObject, { - item.with_borrowed_ptr(self.py(), |item| unsafe { - err::error_on_minusone(self.py(), ffi::PyList_Append(self.as_ptr(), item)) - }) + let py = self.py(); + unsafe { + err::error_on_minusone( + py, + ffi::PyList_Append(self.as_ptr(), item.to_object(py).as_ptr()), + ) + } } /// Inserts an item at the specified index. @@ -235,14 +238,19 @@ impl PyList { /// If `index >= self.len()`, inserts at the end. pub fn insert(&self, index: usize, item: I) -> PyResult<()> where - I: ToBorrowedObject, + I: ToPyObject, { - item.with_borrowed_ptr(self.py(), |item| unsafe { + let py = self.py(); + unsafe { err::error_on_minusone( - self.py(), - ffi::PyList_Insert(self.as_ptr(), get_ssize_index(index), item), + py, + ffi::PyList_Insert( + self.as_ptr(), + get_ssize_index(index), + item.to_object(py).as_ptr(), + ), ) - }) + } } /// Determines if self contains `value`. @@ -251,7 +259,7 @@ impl PyList { #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().contains(value) } @@ -262,7 +270,7 @@ impl PyList { #[inline] pub fn index(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().index(value) } diff --git a/src/types/mapping.rs b/src/types/mapping.rs index 8c861ca0393..897f3ed8220 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -2,10 +2,7 @@ use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::types::{PyAny, PySequence}; -use crate::{ - ffi, AsPyPointer, IntoPyPointer, Py, PyNativeType, PyTryFrom, Python, ToBorrowedObject, - ToPyObject, -}; +use crate::{ffi, AsPyPointer, IntoPyPointer, Py, PyNativeType, PyTryFrom, Python, ToPyObject}; /// Represents a reference to a Python object supporting the mapping protocol. #[repr(transparent)] @@ -38,7 +35,7 @@ impl PyMapping { /// This is equivalent to the Python expression `key in self`. pub fn contains(&self, key: K) -> PyResult where - K: ToBorrowedObject, + K: ToPyObject, { PyAny::contains(self, key) } @@ -51,7 +48,7 @@ impl PyMapping { #[inline] pub fn get_item(&self, key: K) -> PyResult<&PyAny> where - K: ToBorrowedObject, + K: ToPyObject, { PyAny::get_item(self, key) } @@ -74,7 +71,7 @@ impl PyMapping { #[inline] pub fn del_item(&self, key: K) -> PyResult<()> where - K: ToBorrowedObject, + K: ToPyObject, { PyAny::del_item(self, key) } diff --git a/src/types/mod.rs b/src/types/mod.rs index ad79d062b9a..f0419cbe177 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -197,6 +197,7 @@ macro_rules! pyobject_native_type_sized { type BaseNativeType = $name; type ThreadChecker = $crate::impl_::pyclass::ThreadCheckerStub<$crate::PyObject>; type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; + type PyClassMutability = $crate::pycell::ImmutableClass; } } } diff --git a/src/types/module.rs b/src/types/module.rs index 2838734b473..2dadf0f0c70 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -7,7 +7,6 @@ use crate::err::{PyErr, PyResult}; use crate::exceptions; use crate::ffi; use crate::pyclass::PyClass; -use crate::type_object::PyTypeObject; use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString}; use crate::{AsPyPointer, IntoPy, PyObject, Python}; use std::ffi::{CStr, CString}; @@ -291,7 +290,7 @@ impl PyModule { where T: PyClass, { - self.add(T::NAME, ::type_object(self.py())) + self.add(T::NAME, T::type_object(self.py())) } /// Adds a function or a (sub)module to a module, using the functions name as name. @@ -301,11 +300,14 @@ impl PyModule { where T: IntoPyCallbackOutput, { + self._add_wrapped(wrapper(self.py()).convert(self.py())?) + } + + fn _add_wrapped(&self, object: PyObject) -> PyResult<()> { let py = self.py(); - let function = wrapper(py).convert(py)?; - let name = function.getattr(py, __name__(py))?; + let name = object.getattr(py, __name__(py))?; let name = name.extract(py)?; - self.add(name, function) + self.add(name, object) } /// Adds a submodule to a module. diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 7cb5ee9de2d..c45e75a2007 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -3,9 +3,9 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::internal_tricks::get_ssize_index; use crate::types::{PyAny, PyList, PyTuple}; -use crate::{ffi, PyNativeType}; +use crate::{ffi, PyNativeType, ToPyObject}; use crate::{AsPyPointer, IntoPyPointer, Py, Python}; -use crate::{FromPyObject, PyTryFrom, ToBorrowedObject}; +use crate::{FromPyObject, PyTryFrom}; /// Represents a reference to a Python object supporting the sequence protocol. #[repr(transparent)] @@ -123,15 +123,18 @@ impl PySequence { #[inline] pub fn set_item(&self, i: usize, item: I) -> PyResult<()> where - I: ToBorrowedObject, + I: ToPyObject, { + let py = self.py(); unsafe { - item.with_borrowed_ptr(self.py(), |item| { - err::error_on_minusone( - self.py(), - ffi::PySequence_SetItem(self.as_ptr(), get_ssize_index(i), item), - ) - }) + err::error_on_minusone( + py, + ffi::PySequence_SetItem( + self.as_ptr(), + get_ssize_index(i), + item.to_object(py).as_ptr(), + ), + ) } } @@ -185,11 +188,10 @@ impl PySequence { #[cfg(not(PyPy))] pub fn count(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Count(self.as_ptr(), ptr) - }); + let r = + unsafe { ffi::PySequence_Count(self.as_ptr(), value.to_object(self.py()).as_ptr()) }; if r == -1 { Err(PyErr::fetch(self.py())) } else { @@ -203,11 +205,10 @@ impl PySequence { #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Contains(self.as_ptr(), ptr) - }); + let r = + unsafe { ffi::PySequence_Contains(self.as_ptr(), value.to_object(self.py()).as_ptr()) }; match r { 0 => Ok(false), 1 => Ok(true), @@ -221,11 +222,10 @@ impl PySequence { #[inline] pub fn index(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { - let r = value.with_borrowed_ptr(self.py(), |ptr| unsafe { - ffi::PySequence_Index(self.as_ptr(), ptr) - }); + let r = + unsafe { ffi::PySequence_Index(self.as_ptr(), value.to_object(self.py()).as_ptr()) }; if r == -1 { Err(PyErr::fetch(self.py())) } else { diff --git a/src/types/set.rs b/src/types/set.rs index 63dc7c7e24e..d8445c9107b 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -4,9 +4,7 @@ use crate::err::{self, PyErr, PyResult}; #[cfg(Py_LIMITED_API)] use crate::types::PyIterator; -use crate::{ - ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, Python, ToBorrowedObject, ToPyObject, -}; +use crate::{ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyObject, Python, ToPyObject}; use std::cmp; use std::collections::{BTreeSet, HashSet}; use std::{collections, hash, ptr}; @@ -69,13 +67,13 @@ impl PySet { where K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - match ffi::PySet_Contains(self.as_ptr(), key) { + unsafe { + match ffi::PySet_Contains(self.as_ptr(), key.to_object(self.py()).as_ptr()) { 1 => Ok(true), 0 => Ok(false), _ => Err(PyErr::fetch(self.py())), } - }) + } } /// Removes the element from the set if it is present. @@ -83,9 +81,9 @@ impl PySet { where K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - ffi::PySet_Discard(self.as_ptr(), key); - }) + unsafe { + ffi::PySet_Discard(self.as_ptr(), key.to_object(self.py()).as_ptr()); + } } /// Adds an element to the set. @@ -93,9 +91,12 @@ impl PySet { where K: ToPyObject, { - key.with_borrowed_ptr(self.py(), move |key| unsafe { - err::error_on_minusone(self.py(), ffi::PySet_Add(self.as_ptr(), key)) - }) + unsafe { + err::error_on_minusone( + self.py(), + ffi::PySet_Add(self.as_ptr(), key.to_object(self.py()).as_ptr()), + ) + } } /// Removes and returns an arbitrary element from the set. @@ -303,15 +304,15 @@ impl PyFrozenSet { /// This is equivalent to the Python expression `key in self`. pub fn contains(&self, key: K) -> PyResult where - K: ToBorrowedObject, + K: ToPyObject, { - key.with_borrowed_ptr(self.py(), |key| unsafe { - match ffi::PySet_Contains(self.as_ptr(), key) { + unsafe { + match ffi::PySet_Contains(self.as_ptr(), key.to_object(self.py()).as_ptr()) { 1 => Ok(true), 0 => Ok(false), _ => Err(PyErr::fetch(self.py())), } - }) + } } /// Returns an iterator of values in this frozen set. diff --git a/src/types/string.rs b/src/types/string.rs index 4e906f8ce51..1511b794aa2 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -385,7 +385,7 @@ impl FromPyObject<'_> for char { mod tests { use super::*; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] - use crate::type_object::PyTypeObject; + use crate::PyTypeInfo; use crate::Python; use crate::{FromPyObject, PyObject, PyTryFrom, ToPyObject}; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 86454542063..c9f9ba1df0e 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -7,7 +7,7 @@ use crate::internal_tricks::get_ssize_index; use crate::types::PySequence; use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyObject, - PyResult, PyTryFrom, Python, ToBorrowedObject, ToPyObject, + PyResult, PyTryFrom, Python, ToPyObject, }; #[inline] @@ -215,7 +215,7 @@ impl PyTuple { #[inline] pub fn contains(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().contains(value) } @@ -226,7 +226,7 @@ impl PyTuple { #[inline] pub fn index(&self, value: V) -> PyResult where - V: ToBorrowedObject, + V: ToPyObject, { self.as_sequence().index(value) } diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 7c1e1fda8a1..e266bd0f15e 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -3,8 +3,7 @@ // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython use crate::err::{self, PyResult}; -use crate::type_object::PyTypeObject; -use crate::{ffi, AsPyPointer, PyAny, Python}; +use crate::{ffi, AsPyPointer, PyAny, PyTypeInfo, Python}; /// Represents a reference to a Python `type object`. #[repr(transparent)] @@ -15,7 +14,7 @@ pyobject_native_type_core!(PyType, ffi::PyType_Type, #checkfunction=ffi::PyType_ impl PyType { /// Creates a new type object. #[inline] - pub fn new(py: Python<'_>) -> &PyType { + pub fn new(py: Python<'_>) -> &PyType { T::type_object(py) } @@ -55,7 +54,7 @@ impl PyType { /// `T` is known at compile time. pub fn is_subclass_of(&self) -> PyResult where - T: PyTypeObject, + T: PyTypeInfo, { self.is_subclass(T::type_object(self.py())) } @@ -77,9 +76,8 @@ impl PyType { #[cfg(test)] mod tests { use crate::{ - type_object::PyTypeObject, types::{PyBool, PyLong}, - Python, + PyTypeInfo, Python, }; #[test] diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index f82153082f1..989928c2775 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -94,6 +94,7 @@ fn _test_compile_errors() { #[rustversion::since(1.60)] fn tests_rust_1_60(t: &trybuild::TestCases) { + t.compile_fail("tests/ui/invalid_immutable_pyclass_borrow.rs"); t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); t.compile_fail("tests/ui/missing_intopy.rs"); } diff --git a/tests/test_default_impls.rs b/tests/test_default_impls.rs index a764c1f462b..25224aa5681 100644 --- a/tests/test_default_impls.rs +++ b/tests/test_default_impls.rs @@ -30,7 +30,7 @@ enum OverrideSlot { #[pymethods] impl OverrideSlot { fn __repr__(&self) -> &str { - "overriden" + "overridden" } } @@ -38,6 +38,6 @@ impl OverrideSlot { fn test_override_slot() { Python::with_gil(|py| { let test_object = Py::new(py, OverrideSlot::Var).unwrap(); - py_assert!(py, test_object, "repr(test_object) == 'overriden'"); + py_assert!(py, test_object, "repr(test_object) == 'overridden'"); }) } diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 7f7e5bcf9a0..0d134883b4b 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -3,7 +3,7 @@ use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; +use pyo3::PyTypeInfo; use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; diff --git a/tests/test_gc_pyproto.rs b/tests/test_gc_pyproto.rs index 7641975188a..d9c44044d67 100644 --- a/tests/test_gc_pyproto.rs +++ b/tests/test_gc_pyproto.rs @@ -6,8 +6,7 @@ use pyo3::class::PyGCProtocol; use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; -use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto}; +use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto, PyTypeInfo}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index b36a68b13b3..25dae983335 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -1,8 +1,7 @@ #![cfg(feature = "macros")] use pyo3::prelude::*; -use pyo3::py_run; -use pyo3::type_object::PyTypeObject; +use pyo3::{py_run, PyTypeInfo}; use pyo3::types::IntoPyDict; @@ -305,7 +304,7 @@ impl SimpleClass { #[test] fn test_subclass_ref_counts() { // regression test for issue #1363 - use pyo3::type_object::PyTypeObject; + use pyo3::PyTypeInfo; Python::with_gil(|py| { #[allow(non_snake_case)] let SimpleClass = SimpleClass::type_object(py); diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 6b1b3bf1fb9..75b960cdb18 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -962,7 +962,7 @@ pymethods!( // Regression test for issue 1506 - incorrect macro hygiene. // By applying the `#[pymethods]` attribute inside a macro_rules! macro, this separates the macro // call scope from the scope of the impl block. For this to work our macros must be careful to not -// cheat hygeine! +// cheat hygiene! #[pyclass] struct Issue1506 {} diff --git a/tests/test_multiple_pymethods.rs b/tests/test_multiple_pymethods.rs index a793eec5e32..78a77334a1e 100644 --- a/tests/test_multiple_pymethods.rs +++ b/tests/test_multiple_pymethods.rs @@ -1,8 +1,8 @@ #![cfg(feature = "multiple-pymethods")] use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; use pyo3::types::PyType; +use pyo3::PyTypeInfo; #[macro_use] mod common; diff --git a/tests/test_mutable_pyclass.rs b/tests/test_mutable_pyclass.rs new file mode 100644 index 00000000000..8d5072e50fc --- /dev/null +++ b/tests/test_mutable_pyclass.rs @@ -0,0 +1,201 @@ +#![cfg(feature = "macros")] + +use pyo3::impl_::pyclass::{PyClassBaseType, PyClassImpl}; +use pyo3::prelude::*; +use pyo3::pycell::{ + BorrowChecker, ExtendsMutableAncestor, ImmutableClass, MutableClass, PyClassMutability, +}; +use pyo3::PyClass; + +#[pyclass(subclass)] +struct MutableBase; + +#[pyclass(extends = MutableBase, subclass)] +struct MutableChildOfMutableBase; + +#[pyclass(extends = MutableBase, immutable, subclass)] +struct ImmutableChildOfMutableBase; + +#[pyclass(extends = MutableChildOfMutableBase)] +struct MutableChildOfMutableChildOfMutableBase; + +#[pyclass(extends = ImmutableChildOfMutableBase)] +struct MutableChildOfImmutableChildOfMutableBase; + +#[pyclass(extends = MutableChildOfMutableBase, immutable)] +struct ImmutableChildOfMutableChildOfMutableBase; + +#[pyclass(extends = ImmutableChildOfMutableBase, immutable)] +struct ImmutableChildOfImmutableChildOfMutableBase; + +#[pyclass(immutable, subclass)] +struct ImmutableBase; + +#[pyclass(extends = ImmutableBase, subclass)] +struct MutableChildOfImmutableBase; + +#[pyclass(extends = ImmutableBase, immutable, subclass)] +struct ImmutableChildOfImmutableBase; + +#[pyclass(extends = MutableChildOfImmutableBase)] +struct MutableChildOfMutableChildOfImmutableBase; + +#[pyclass(extends = ImmutableChildOfImmutableBase)] +struct MutableChildOfImmutableChildOfImmutableBase; + +#[pyclass(extends = MutableChildOfImmutableBase, immutable)] +struct ImmutableChildOfMutableChildOfImmutableBase; + +#[pyclass(extends = ImmutableChildOfImmutableBase, immutable)] +struct ImmutableChildOfImmutableChildOfImmutableBase; + +fn assert_mutable>() {} +fn assert_immutable>() {} +fn assert_mutable_with_mutable_ancestor< + T: PyClass>, +>() +// These horrible bounds are necessary for Rust 1.48 but not newer versions +where + ::BaseType: PyClassImpl>, + <::BaseType as PyClassImpl>::PyClassMutability: + PyClassMutability, + ::BaseType: PyClassBaseType>, +{ +} +fn assert_immutable_with_mutable_ancestor< + T: PyClass>, +>() +// These horrible bounds are necessary for Rust 1.48 but not newer versions +where + ::BaseType: PyClassImpl>, + <::BaseType as PyClassImpl>::PyClassMutability: + PyClassMutability, + ::BaseType: PyClassBaseType>, +{ +} + +#[test] +fn test_inherited_mutability() { + // mutable base + assert_mutable::(); + + // children of mutable base have a mutable ancestor + assert_mutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); + + // grandchildren of mutable base have a mutable ancestor + assert_mutable_with_mutable_ancestor::(); + assert_mutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); + + // immutable base and children + assert_immutable::(); + assert_immutable::(); + assert_immutable::(); + + // mutable children of immutable at any level are simply mutable + assert_mutable::(); + assert_mutable::(); + + // children of the mutable child display this property + assert_mutable_with_mutable_ancestor::(); + assert_immutable_with_mutable_ancestor::(); +} + +#[test] +fn test_mutable_borrow_prevents_further_borrows() { + Python::with_gil(|py| { + let mmm = Py::new( + py, + PyClassInitializer::from(MutableBase) + .add_subclass(MutableChildOfMutableBase) + .add_subclass(MutableChildOfMutableChildOfMutableBase), + ) + .unwrap(); + + let mmm_cell: &PyCell = mmm.as_ref(py); + + let mmm_refmut = mmm_cell.borrow_mut(); + + // Cannot take any other mutable or immutable borrows whilst the object is borrowed mutably + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell.extract::>().is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell.extract::>().is_err()); + + // With the borrow dropped, all other borrow attempts will succeed + drop(mmm_refmut); + + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + }) +} + +#[test] +fn test_immutable_borrows_prevent_mutable_borrows() { + Python::with_gil(|py| { + let mmm = Py::new( + py, + PyClassInitializer::from(MutableBase) + .add_subclass(MutableChildOfMutableBase) + .add_subclass(MutableChildOfMutableChildOfMutableBase), + ) + .unwrap(); + + let mmm_cell: &PyCell = mmm.as_ref(py); + + let mmm_refmut = mmm_cell.borrow(); + + // Further immutable borrows are ok + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + + // Further mutable borrows are not ok + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell + .extract::>() + .is_err()); + assert!(mmm_cell.extract::>().is_err()); + + // With the borrow dropped, all mutable borrow attempts will succeed + drop(mmm_refmut); + + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell + .extract::>() + .is_ok()); + assert!(mmm_cell.extract::>().is_ok()); + }) +} diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr index 360a0a45bc3..cf8c2218c6a 100644 --- a/tests/ui/abi3_nativetype_inheritance.stderr +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -1,12 +1,3 @@ -error[E0277]: the trait bound `PyDict: PyClass` is not satisfied - --> tests/ui/abi3_nativetype_inheritance.rs:5:1 - | -5 | #[pyclass(extends=PyDict)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` - | - = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` - = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0277]: the trait bound `PyDict: PyClass` is not satisfied --> tests/ui/abi3_nativetype_inheritance.rs:5:1 | @@ -17,6 +8,15 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied note: required by a bound in `ThreadCheckerInherited` --> src/impl_/pyclass.rs | - | pub struct ThreadCheckerInherited(PhantomData, U::ThreadChecker); - | ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` + | pub struct ThreadCheckerInherited( + | ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `PyDict: PyClass` is not satisfied + --> tests/ui/abi3_nativetype_inheritance.rs:5:1 + | +5 | #[pyclass(extends=PyDict)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict` + | + = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` + = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/invalid_immutable_pyclass_borrow.rs b/tests/ui/invalid_immutable_pyclass_borrow.rs new file mode 100644 index 00000000000..a50e7ffded2 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.rs @@ -0,0 +1,23 @@ +use pyo3::prelude::*; + +#[pyclass(immutable)] +pub struct Foo { + #[pyo3(get)] + field: u32, +} + +fn borrow_mut_fails(foo: Py, py: Python){ + let borrow = foo.as_ref(py).borrow_mut(); +} + +#[pyclass(subclass)] +struct MutableBase; + +#[pyclass(immutable, extends = MutableBase)] +struct ImmutableChild; + +fn borrow_mut_of_child_fails(child: Py, py: Python){ + let borrow = child.as_ref(py).borrow_mut(); +} + +fn main(){} diff --git a/tests/ui/invalid_immutable_pyclass_borrow.stderr b/tests/ui/invalid_immutable_pyclass_borrow.stderr new file mode 100644 index 00000000000..2b0653a9251 --- /dev/null +++ b/tests/ui/invalid_immutable_pyclass_borrow.stderr @@ -0,0 +1,25 @@ +error[E0271]: type mismatch resolving `::Mutability == Mutable` + --> tests/ui/invalid_immutable_pyclass_borrow.rs:10:33 + | +10 | let borrow = foo.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ expected struct `Mutable`, found struct `Immutable` + | + = note: required because of the requirements on the impl of `MutablePyClass` for `Foo` +note: required by a bound in `PyCell::::borrow_mut` + --> src/pycell.rs + | + | T: MutablePyClass, + | ^^^^^^^^^^^^^^ required by this bound in `PyCell::::borrow_mut` + +error[E0271]: type mismatch resolving `::Mutability == Mutable` + --> tests/ui/invalid_immutable_pyclass_borrow.rs:20:35 + | +20 | let borrow = child.as_ref(py).borrow_mut(); + | ^^^^^^^^^^ expected struct `Mutable`, found struct `Immutable` + | + = note: required because of the requirements on the impl of `MutablePyClass` for `ImmutableChild` +note: required by a bound in `PyCell::::borrow_mut` + --> src/pycell.rs + | + | T: MutablePyClass, + | ^^^^^^^^^^^^^^ required by this bound in `PyCell::::borrow_mut` diff --git a/tests/ui/invalid_pyclass_args.stderr b/tests/ui/invalid_pyclass_args.stderr index 2fc642d8ba0..1fd61d5c364 100644 --- a/tests/ui/invalid_pyclass_args.stderr +++ b/tests/ui/invalid_pyclass_args.stderr @@ -1,4 +1,4 @@ -error: expected one of: `crate`, `dict`, `extends`, `freelist`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` --> tests/ui/invalid_pyclass_args.rs:3:11 | 3 | #[pyclass(extend=pyo3::types::PyDict)] @@ -34,7 +34,7 @@ error: expected string literal 18 | #[pyclass(module = my_module)] | ^^^^^^^^^ -error: expected one of: `crate`, `dict`, `extends`, `freelist`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` +error: expected one of: `crate`, `dict`, `extends`, `freelist`, `immutable`, `mapping`, `module`, `name`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc` --> tests/ui/invalid_pyclass_args.rs:21:11 | 21 | #[pyclass(weakrev)]