From 7e2d3117ceabf0dd66b37913eb94699c24485ab5 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:37:45 +0100 Subject: [PATCH] cleanup: deprecate PyTypeObject trait --- Architecture.md | 6 ++-- CHANGELOG.md | 3 +- guide/src/class.md | 3 +- guide/src/class/numeric.md | 36 +++++++++++----------- guide/src/migration.md | 31 +++++++++++++++++++ src/err/err_state.rs | 3 +- src/err/mod.rs | 6 ++-- src/exceptions.rs | 2 +- src/impl_/extract_argument.rs | 3 +- src/impl_/pyclass.rs | 4 +-- src/marker.rs | 7 +++-- src/type_object.rs | 53 +++++++++++++++++++++----------- src/types/any.rs | 6 ++-- src/types/module.rs | 3 +- src/types/string.rs | 2 +- src/types/typeobject.rs | 10 +++--- tests/test_gc.rs | 2 +- tests/test_gc_pyproto.rs | 3 +- tests/test_inheritance.rs | 5 ++- tests/test_multiple_pymethods.rs | 2 +- 20 files changed, 116 insertions(+), 74 deletions(-) diff --git a/Architecture.md b/Architecture.md index 4fb84f4f05a..5f676b19200 100644 --- a/Architecture.md +++ b/Architecture.md @@ -135,12 +135,12 @@ to ensure Rust's borrow rules. See [the documentation](https://docs.rs/pyo3/latest/pyo3/pycell/struct.PyCell.html) for more. `PyCell` requires that `T` implements `PyClass`. -This trait is somewhat complex and derives many traits, but the most important one is `PyTypeObject` +This trait is somewhat complex and derives many traits, but the most important one is `PyTypeInfo` in [`src/type_object.rs`]. -`PyTypeObject` is also implemented for built-in types. +`PyTypeInfo` is also implemented for built-in types. In Python, all objects have their types, and types are also objects of `type`. For example, you can see `type({})` shows `dict` and `type(type({}))` shows `type` in Python REPL. -`T: PyTypeObject` implies that `T` has a corresponding type object. +`T: PyTypeInfo` implies that `T` has a corresponding type object. ## 4. Protocol methods diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6c484bcdd..1fdd474dafe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ PyO3 versions, please see the [migration guide](https://pyo3.rs/latest/migration The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [Unreleased] ### Added @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Move `PyTypeObject::type_object` method to `PyTypeInfo` trait, and deprecate `PyTypeObject` trait. [#2284](https://github.com/PyO3/pyo3/pull/2284) - The deprecated `pyproto` feature is now disabled by default. [#2321](https://github.com/PyO3/pyo3/pull/2321) ## [0.16.4] - 2022-04-14 diff --git a/guide/src/class.md b/guide/src/class.md index 81802df7d32..e5245be98fe 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -52,7 +52,7 @@ enum MyEnum { Because Python objects are freely shared between threads by the Python interpreter, all types annotated with `#[pyclass]` must implement `Send` (unless annotated with [`#[pyclass(unsendable)]`](#customizing-the-class)). -The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`], and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. +The above example generates implementations for [`PyTypeInfo`] and [`PyClass`] for `MyClass` and `MyEnum`. To see these generated implementations, refer to the [implementation details](#implementation-details) at the end of this chapter. ## Constructor @@ -1004,7 +1004,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass { [`GILGuard`]: {{#PYO3_DOCS_URL}}/pyo3/struct.GILGuard.html [`PyTypeInfo`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeInfo.html -[`PyTypeObject`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeObject.html [`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html [`PyClass`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/trait.PyClass.html diff --git a/guide/src/class/numeric.md b/guide/src/class/numeric.md index a93d2239add..bb4ef1f45d3 100644 --- a/guide/src/class/numeric.md +++ b/guide/src/class/numeric.md @@ -7,7 +7,7 @@ Before proceeding, we should think about how we want to handle overflows. There be reinventing the wheel. - We can raise exceptions whenever `Number` overflows, but that makes the API painful to use. - We can wrap around the boundary of `i32`. This is the approach we'll take here. To do that we'll just forward to `i32`'s - `wrapping_*` methods. + `wrapping_*` methods. ### Fixing our constructor @@ -336,39 +336,39 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { # def hash_djb2(s: str): # n = Number(0) # five = Number(5) -# +# # for x in s: # n = Number(ord(x)) + ((n << five) - n) # return n -# +# # assert hash_djb2('l50_50') == Number(-1152549421) # assert hash_djb2('logo') == Number(3327403) # assert hash_djb2('horizon') == Number(1097468315) -# -# +# +# # assert Number(2) + Number(2) == Number(4) # assert Number(2) + Number(2) != Number(5) -# +# # assert Number(13) - Number(7) == Number(6) # assert Number(13) - Number(-7) == Number(20) -# +# # assert Number(13) / Number(7) == Number(1) # assert Number(13) // Number(7) == Number(1) -# +# # assert Number(13) * Number(7) == Number(13*7) -# +# # assert Number(13) > Number(7) # assert Number(13) < Number(20) # assert Number(13) == Number(13) # assert Number(13) >= Number(7) # assert Number(13) <= Number(20) # assert Number(13) == Number(13) -# -# +# +# # assert (True if Number(1) else False) # assert (False if Number(0) else True) -# -# +# +# # assert int(Number(13)) == 13 # assert float(Number(13)) == 13 # assert Number.__doc__ == "Did you ever hear the tragedy of Darth Signed The Overfloweth? I thought not.\nIt's not a story C would tell you. It's a Rust legend." @@ -383,14 +383,14 @@ fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { # assert Number(1337).__repr__() == 'Number(1337)' "#; -# -# use pyo3::type_object::PyTypeObject; -# +# +# use pyo3::PyTypeInfo; +# # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { # let globals = PyModule::import(py, "__main__")?.dict(); # globals.set_item("Number", Number::type_object(py))?; -# +# # py.run(SCRIPT, Some(globals), None)?; # Ok(()) # }) @@ -446,4 +446,4 @@ fn wrap(obj: &PyAny) -> Result { [`PyErr::take`]: https://docs.rs/pyo3/latest/pyo3/prelude/struct.PyErr.html#method.take [`Python`]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html [`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html -[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html \ No newline at end of file +[`pyo3::ffi::PyLong_AsUnsignedLongMask`]: https://docs.rs/pyo3/latest/pyo3/ffi/fn.PyLong_AsUnsignedLongMask.html diff --git a/guide/src/migration.md b/guide/src/migration.md index 725ff2cb3b3..3aa50f7f218 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -9,6 +9,37 @@ For a detailed list of all changes, see the [CHANGELOG](changelog.md). In preparation for removing the deprecated `#[pyproto]` attribute macro in a future PyO3 version, it is now gated behind an opt-in feature flag. This also gives a slight saving to compile times for code which does not use the deprecated macro. +### `PyTypeObject` trait has been deprecated + +The `PyTypeObject` trait already was near-useless; almost all functionality was already on the `PyTypeInfo` trait, which `PyTypeObject` had a blanket implementation based upon. In PyO3 0.17 the final method, `PyTypeObject::type_object` was moved to `PyTypeInfo::type_object`. + +To migrate, update trait bounds and imports from `PyTypeObject` to `PyTypeInfo`. + +Before: + +```rust,ignore +use pyo3::Python; +use pyo3::type_object::PyTypeObject; +use pyo3::types::PyType; + +fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) +} +``` + +After + +```rust +use pyo3::{Python, PyTypeInfo}; +use pyo3::types::PyType; + +fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) +} + +# Python::with_gil(|py| { get_type_object::(py); }); +``` + ## from 0.15.* to 0.16 ### Drop support for older technologies diff --git a/src/err/err_state.rs b/src/err/err_state.rs index ecebeb5a622..6193b9cbf62 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -1,9 +1,8 @@ use crate::{ exceptions::{PyBaseException, PyTypeError}, ffi, - type_object::PyTypeObject, types::{PyTraceback, PyType}, - AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, Python, + AsPyPointer, IntoPy, IntoPyPointer, Py, PyObject, PyTypeInfo, Python, }; #[derive(Clone)] diff --git a/src/err/mod.rs b/src/err/mod.rs index eed6798ad58..425c044cb9b 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -1,7 +1,7 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::panic::PanicException; -use crate::type_object::PyTypeObject; +use crate::type_object::PyTypeInfo; use crate::types::{PyTraceback, PyType}; use crate::{ exceptions::{self, PyBaseException}, @@ -92,7 +92,7 @@ impl PyErr { #[inline] pub fn new(args: A) -> PyErr where - T: PyTypeObject, + T: PyTypeInfo, A: PyErrArguments + Send + Sync + 'static, { PyErr::from_state(PyErrState::LazyTypeAndValue { @@ -428,7 +428,7 @@ impl PyErr { #[inline] pub fn is_instance_of(&self, py: Python<'_>) -> bool where - T: PyTypeObject, + T: PyTypeInfo, { self.is_instance(py, T::type_object(py)) } diff --git a/src/exceptions.rs b/src/exceptions.rs index d8d65de52ee..6f7c8233e19 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -218,7 +218,7 @@ macro_rules! create_exception { }; } -/// `impl $crate::type_object::PyTypeObject for $name` where `$name` is an +/// `impl PyTypeInfo for $name` where `$name` is an /// exception newly defined in Rust code. #[doc(hidden)] #[macro_export] diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index b793deef72b..feea25f1409 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -1,9 +1,8 @@ use crate::{ exceptions::PyTypeError, ffi, - type_object::PyTypeObject, types::{PyDict, PyString, PyTuple}, - FromPyObject, PyAny, PyErr, PyResult, Python, + FromPyObject, PyAny, PyErr, PyResult, PyTypeInfo, Python, }; /// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument. diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 43468de9e48..60316befe27 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -4,7 +4,7 @@ use crate::{ impl_::freelist::FreeList, pycell::{GetBorrowChecker, Mutability, PyCellLayout, PyClassMutability}, pyclass_init::PyObjectInit, - type_object::{PyLayout, PyTypeObject}, + type_object::PyLayout, Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python, }; use std::{ @@ -162,7 +162,7 @@ pub trait PyClassImpl: Sized { type Layout: PyLayout; /// Base class - type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType; + type BaseType: PyTypeInfo + PyClassBaseType; /// Immutable or mutable type Mutability: Mutability; diff --git a/src/marker.rs b/src/marker.rs index f159d89e85e..209fe33b325 100644 --- a/src/marker.rs +++ b/src/marker.rs @@ -122,10 +122,11 @@ use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::gil::{self, GILGuard, GILPool}; use crate::impl_::not_send::NotSend; -use crate::type_object::{PyTypeInfo, PyTypeObject}; use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::version::PythonVersionInfo; -use crate::{ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom}; +use crate::{ + ffi, AsPyPointer, FromPyPointer, IntoPyPointer, PyNativeType, PyObject, PyTryFrom, PyTypeInfo, +}; use std::ffi::{CStr, CString}; use std::marker::PhantomData; use std::os::raw::c_int; @@ -583,7 +584,7 @@ impl<'py> Python<'py> { /// Gets the Python type object for type `T`. pub fn get_type(self) -> &'py PyType where - T: PyTypeObject, + T: PyTypeInfo, { T::type_object(self) } diff --git a/src/type_object.rs b/src/type_object.rs index b8c2d654133..f2743d145ab 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -51,9 +51,14 @@ pub unsafe trait PyTypeInfo: Sized { /// Utility type to make Py::as_ref work. type AsRefTarget: PyNativeType; - /// PyTypeObject instance for this type. + /// Returns the PyTypeObject instance for this type. fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject; + /// Returns the safe abstraction over the type object. + fn type_object(py: Python<'_>) -> &PyType { + unsafe { py.from_borrowed_ptr(Self::type_object_raw(py) as _) } + } + /// Checks if `object` is an instance of this type or a subclass of this type. fn is_type_of(object: &PyAny) -> bool { unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 } @@ -65,27 +70,19 @@ pub unsafe trait PyTypeInfo: Sized { } } -/// Python object types that have a corresponding type object. +/// Legacy trait which previously held the `type_object` method now found on `PyTypeInfo`. /// /// # Safety /// -/// This trait is marked unsafe because not fulfilling the contract for type_object -/// leads to UB. -/// -/// See also [PyTypeInfo::type_object_raw](trait.PyTypeInfo.html#tymethod.type_object_raw). -pub unsafe trait PyTypeObject { - /// Returns the safe abstraction over the type object. - fn type_object(py: Python<'_>) -> &PyType; -} +/// This trait used to have stringent safety requirements, but they are now irrelevant as it is deprecated. +#[deprecated( + since = "0.17.0", + note = "PyTypeObject::type_object was moved to PyTypeInfo::type_object" +)] +pub unsafe trait PyTypeObject: PyTypeInfo {} -unsafe impl PyTypeObject for T -where - T: PyTypeInfo, -{ - fn type_object(py: Python<'_>) -> &PyType { - unsafe { py.from_borrowed_ptr(Self::type_object_raw(py) as _) } - } -} +#[allow(deprecated)] +unsafe impl PyTypeObject for T {} /// Lazy type object for PyClass. #[doc(hidden)] @@ -232,3 +229,23 @@ pub(crate) unsafe fn get_tp_free(tp: *mut ffi::PyTypeObject) -> ffi::freefunc { std::mem::transmute(ptr) } } + +#[cfg(test)] +mod tests { + #[test] + #[allow(deprecated)] + fn test_deprecated_type_object() { + // Even though PyTypeObject is deprecated, simple usages of it as a trait bound should continue to work. + use super::PyTypeObject; + use crate::types::{PyList, PyType}; + use crate::Python; + + fn get_type_object(py: Python<'_>) -> &PyType { + T::type_object(py) + } + + Python::with_gil(|py| { + assert!(get_type_object::(py).is(::type_object(py))) + }); + } +} diff --git a/src/types/any.rs b/src/types/any.rs index 9ba9b515944..f49b8efa617 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -4,7 +4,7 @@ use crate::conversion::{ }; use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::exceptions::PyTypeError; -use crate::type_object::PyTypeObject; +use crate::type_object::PyTypeInfo; use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; use crate::{err, ffi, Py, PyNativeType, PyObject, Python}; use std::cell::UnsafeCell; @@ -782,7 +782,7 @@ impl PyAny { /// /// This is equivalent to the Python expression `isinstance(self, T)`, /// if the type `T` is known at compile time. - pub fn is_instance_of(&self) -> PyResult { + pub fn is_instance_of(&self) -> PyResult { self.is_instance(T::type_object(self.py())) } @@ -814,7 +814,7 @@ impl PyAny { #[cfg(test)] mod tests { use crate::{ - type_object::PyTypeObject, + type_object::PyTypeInfo, types::{IntoPyDict, PyList, PyLong, PyModule}, Python, ToPyObject, }; diff --git a/src/types/module.rs b/src/types/module.rs index 2838734b473..21f6e0cae32 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -7,7 +7,6 @@ use crate::err::{PyErr, PyResult}; use crate::exceptions; use crate::ffi; use crate::pyclass::PyClass; -use crate::type_object::PyTypeObject; use crate::types::{PyAny, PyCFunction, PyDict, PyList, PyString}; use crate::{AsPyPointer, IntoPy, PyObject, Python}; use std::ffi::{CStr, CString}; @@ -291,7 +290,7 @@ impl PyModule { where T: PyClass, { - self.add(T::NAME, ::type_object(self.py())) + self.add(T::NAME, T::type_object(self.py())) } /// Adds a function or a (sub)module to a module, using the functions name as name. diff --git a/src/types/string.rs b/src/types/string.rs index 4e906f8ce51..1511b794aa2 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -385,7 +385,7 @@ impl FromPyObject<'_> for char { mod tests { use super::*; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] - use crate::type_object::PyTypeObject; + use crate::PyTypeInfo; use crate::Python; use crate::{FromPyObject, PyObject, PyTryFrom, ToPyObject}; #[cfg(all(not(Py_LIMITED_API), target_endian = "little"))] diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 7c1e1fda8a1..e266bd0f15e 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -3,8 +3,7 @@ // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython use crate::err::{self, PyResult}; -use crate::type_object::PyTypeObject; -use crate::{ffi, AsPyPointer, PyAny, Python}; +use crate::{ffi, AsPyPointer, PyAny, PyTypeInfo, Python}; /// Represents a reference to a Python `type object`. #[repr(transparent)] @@ -15,7 +14,7 @@ pyobject_native_type_core!(PyType, ffi::PyType_Type, #checkfunction=ffi::PyType_ impl PyType { /// Creates a new type object. #[inline] - pub fn new(py: Python<'_>) -> &PyType { + pub fn new(py: Python<'_>) -> &PyType { T::type_object(py) } @@ -55,7 +54,7 @@ impl PyType { /// `T` is known at compile time. pub fn is_subclass_of(&self) -> PyResult where - T: PyTypeObject, + T: PyTypeInfo, { self.is_subclass(T::type_object(self.py())) } @@ -77,9 +76,8 @@ impl PyType { #[cfg(test)] mod tests { use crate::{ - type_object::PyTypeObject, types::{PyBool, PyLong}, - Python, + PyTypeInfo, Python, }; #[test] diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 7f7e5bcf9a0..0d134883b4b 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -3,7 +3,7 @@ use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; +use pyo3::PyTypeInfo; use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; diff --git a/tests/test_gc_pyproto.rs b/tests/test_gc_pyproto.rs index 7641975188a..d9c44044d67 100644 --- a/tests/test_gc_pyproto.rs +++ b/tests/test_gc_pyproto.rs @@ -6,8 +6,7 @@ use pyo3::class::PyGCProtocol; use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; -use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto}; +use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto, PyTypeInfo}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index b36a68b13b3..25dae983335 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -1,8 +1,7 @@ #![cfg(feature = "macros")] use pyo3::prelude::*; -use pyo3::py_run; -use pyo3::type_object::PyTypeObject; +use pyo3::{py_run, PyTypeInfo}; use pyo3::types::IntoPyDict; @@ -305,7 +304,7 @@ impl SimpleClass { #[test] fn test_subclass_ref_counts() { // regression test for issue #1363 - use pyo3::type_object::PyTypeObject; + use pyo3::PyTypeInfo; Python::with_gil(|py| { #[allow(non_snake_case)] let SimpleClass = SimpleClass::type_object(py); diff --git a/tests/test_multiple_pymethods.rs b/tests/test_multiple_pymethods.rs index a793eec5e32..78a77334a1e 100644 --- a/tests/test_multiple_pymethods.rs +++ b/tests/test_multiple_pymethods.rs @@ -1,8 +1,8 @@ #![cfg(feature = "multiple-pymethods")] use pyo3::prelude::*; -use pyo3::type_object::PyTypeObject; use pyo3::types::PyType; +use pyo3::PyTypeInfo; #[macro_use] mod common;