diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef6eff35de..5665a29b405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,15 +11,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * The blanket implementations for `FromPyObject` for `&T` and `&mut T` are no longer specializable. Implement `PyTryFrom` for your type to control the behavior of `FromPyObject::extract()` for your types. * The implementation for `IntoPy for T` where `U: FromPy` is no longer specializable. Control the behavior of this via the implementation of `FromPy`. +* `#[new]` does not take `PyRawObject` and can reutrn `Self` [#683](https://github.com/PyO3/pyo3/pull/683) ### Added * Implemented `IntoIterator` for `PySet` and `PyFrozenSet`. [#716](https://github.com/PyO3/pyo3/pull/716) +* `PyClass`, `PyClassShell`, `PyObjectLayout`, `PyClassInitializer` [#683](https://github.com/PyO3/pyo3/pull/683) ### Fixed * Clear error indicator when the exception is handled on the Rust side. [#719](https://github.com/PyO3/pyo3/pull/719) +### Removed + +* `PyRef`, `PyRefMut`, `PyRawObject` [#683](https://github.com/PyO3/pyo3/pull/683) + ## [0.8.5] * Support for `#[name = "foo"]` attribute for `#[pyfunction]` and in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692) diff --git a/examples/rustapi_module/src/buf_and_str.rs b/examples/rustapi_module/src/buf_and_str.rs index c21c6db3d39..24da9025421 100644 --- a/examples/rustapi_module/src/buf_and_str.rs +++ b/examples/rustapi_module/src/buf_and_str.rs @@ -9,8 +9,8 @@ struct BytesExtractor {} #[pymethods] impl BytesExtractor { #[new] - pub fn __new__(obj: &PyRawObject) { - obj.init({ BytesExtractor {} }); + pub fn __new__() -> Self { + BytesExtractor {} } pub fn from_bytes(&mut self, bytes: &PyBytes) -> PyResult { diff --git a/examples/rustapi_module/src/datetime.rs b/examples/rustapi_module/src/datetime.rs index 677eab1fa00..cc20b6c67cf 100644 --- a/examples/rustapi_module/src/datetime.rs +++ b/examples/rustapi_module/src/datetime.rs @@ -195,8 +195,8 @@ pub struct TzClass {} #[pymethods] impl TzClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(TzClass {}) + fn new() -> Self { + TzClass {} } fn utcoffset<'p>(&self, py: Python<'p>, _dt: &PyDateTime) -> PyResult<&'p PyDelta> { diff --git a/examples/rustapi_module/src/dict_iter.rs b/examples/rustapi_module/src/dict_iter.rs index c102cecb94b..1911ac63b5c 100644 --- a/examples/rustapi_module/src/dict_iter.rs +++ b/examples/rustapi_module/src/dict_iter.rs @@ -16,8 +16,8 @@ pub struct DictSize { #[pymethods] impl DictSize { #[new] - fn new(obj: &PyRawObject, expected: u32) { - obj.init(DictSize { expected }) + fn new(expected: u32) -> Self { + DictSize { expected } } fn iter_dict(&mut self, _py: Python<'_>, dict: &PyDict) -> PyResult { diff --git a/examples/rustapi_module/src/othermod.rs b/examples/rustapi_module/src/othermod.rs index dd43694dbbd..20745b29fb6 100644 --- a/examples/rustapi_module/src/othermod.rs +++ b/examples/rustapi_module/src/othermod.rs @@ -13,10 +13,10 @@ pub struct ModClass { #[pymethods] impl ModClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(ModClass { + fn new() -> Self { + ModClass { _somefield: String::from("contents"), - }) + } } fn noop(&self, x: usize) -> usize { diff --git a/examples/rustapi_module/src/subclassing.rs b/examples/rustapi_module/src/subclassing.rs index 4f7abf34617..61ead72fe1e 100644 --- a/examples/rustapi_module/src/subclassing.rs +++ b/examples/rustapi_module/src/subclassing.rs @@ -8,8 +8,8 @@ pub struct Subclassable {} #[pymethods] impl Subclassable { #[new] - fn new(obj: &PyRawObject) { - obj.init(Subclassable {}); + fn new() -> Self { + Subclassable {} } } diff --git a/examples/word-count/src/lib.rs b/examples/word-count/src/lib.rs index 29f97808c0f..cf9b21e69fc 100644 --- a/examples/word-count/src/lib.rs +++ b/examples/word-count/src/lib.rs @@ -16,10 +16,10 @@ struct WordCounter { #[pymethods] impl WordCounter { #[new] - fn new(obj: &PyRawObject, path: String) { - obj.init(WordCounter { + fn new(path: String) -> Self { + WordCounter { path: PathBuf::from(path), - }); + } } /// Searches for the word, parallelized by rayon diff --git a/guide/src/class.md b/guide/src/class.md index 214d9b81b8e..6831c3406a9 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -14,7 +14,77 @@ struct MyClass { debug: bool, } ``` -The above example generates implementations for `PyTypeInfo` and `PyTypeObject` for `MyClass`. + +The above example generates implementations for `PyTypeInfo`, `PyTypeObject` +and `PyClass` for `MyClass`. + +Specifically, the following implementation is generated: + +```rust +use pyo3::prelude::*; + +/// Class for demonstration +struct MyClass { + num: i32, + debug: bool, +} + +impl pyo3::pyclass::PyClassAlloc for MyClass {} + +impl pyo3::PyTypeInfo for MyClass { + type Type = MyClass; + type BaseType = pyo3::types::PyAny; + type ConcreteLayout = pyo3::PyClassShell; + type Initializer = pyo3::PyClassInitializer; + + const NAME: &'static str = "MyClass"; + const MODULE: Option<&'static str> = None; + const DESCRIPTION: &'static str = "Class for demonstration"; + const FLAGS: usize = 0; + + #[inline] + unsafe fn type_object() -> &'static mut pyo3::ffi::PyTypeObject { + static mut TYPE_OBJECT: pyo3::ffi::PyTypeObject = pyo3::ffi::PyTypeObject_INIT; + &mut TYPE_OBJECT + } +} + +impl pyo3::pyclass::PyClass for MyClass { + type Dict = pyo3::pyclass_slots::PyClassDummySlot; + type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; +} + +impl pyo3::IntoPy for MyClass { + fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { + pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) + } +} + +pub struct MyClassGeneratedPyo3Inventory { + methods: &'static [pyo3::class::PyMethodDefType], +} + +impl pyo3::class::methods::PyMethodsInventory for MyClassGeneratedPyo3Inventory { + fn new(methods: &'static [pyo3::class::PyMethodDefType]) -> Self { + Self { methods } + } + + fn get_methods(&self) -> &'static [pyo3::class::PyMethodDefType] { + self.methods + } +} + +impl pyo3::class::methods::PyMethodsInventoryDispatch for MyClass { + type InventoryType = MyClassGeneratedPyo3Inventory; +} + +pyo3::inventory::collect!(MyClassGeneratedPyo3Inventory); + +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# let cls = py.get_type::(); +# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") +``` ## Add class to module @@ -35,27 +105,22 @@ fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> { ``` ## Get Python objects from `pyclass` +You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it). -You can use `pyclass`es like normal rust structs. - -However, if instantiated normally, you can't treat `pyclass`es as Python objects. - -To get a Python object which includes `pyclass`, we have to use some special methods. +For getting *GIL-bounded* (i.e., with `'py` lifetime) references of `pyclass`, +you can use `PyClassShell`. +Or you can use `Py` directly, for *not-GIL-bounded* references. -### `PyRef` +### `PyClassShell` +`PyClassShell` represents the actual layout of `pyclass` on the Python heap. -`PyRef` is a special reference, which ensures that the referred struct is a part of -a Python object, and you are also holding the GIL. +If you want to instantiate `pyclass` in Python and get the reference, +you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. -You can get an instance of `PyRef` by `PyRef::new`, which does 3 things: -1. Allocates a Python object in the Python heap -2. Copies the Rust struct into the Python object -3. Returns a reference to it - -You can use `PyRef` just like `&T`, because it implements `Deref`. ```rust # use pyo3::prelude::*; # use pyo3::types::PyDict; +# use pyo3::PyClassShell; #[pyclass] struct MyClass { num: i32, @@ -63,26 +128,15 @@ struct MyClass { } let gil = Python::acquire_gil(); let py = gil.python(); -let obj = PyRef::new(py, MyClass { num: 3, debug: true }).unwrap(); +let obj = PyClassShell::new_ref(py, MyClass { num: 3, debug: true }).unwrap(); +// You can use deref assert_eq!(obj.num, 3); let dict = PyDict::new(py); -// You can treat a `PyRef` as a Python object +// You can treat a `&PyClassShell` as a normal Python object dict.set_item("obj", obj).unwrap(); -``` - -### `PyRefMut` -`PyRefMut` is a mutable version of `PyRef`. -```rust -# use pyo3::prelude::*; -#[pyclass] -struct MyClass { - num: i32, - debug: bool, -} -let gil = Python::acquire_gil(); -let py = gil.python(); -let mut obj = PyRefMut::new(py, MyClass { num: 3, debug: true }).unwrap(); +// return &mut PyClassShell +let obj = PyClassShell::new_mut(py, MyClass { num: 3, debug: true }).unwrap(); obj.num = 5; ``` @@ -132,7 +186,6 @@ attribute. Only Python's `__new__` method can be specified, `__init__` is not av ```rust # use pyo3::prelude::*; -# use pyo3::PyRawObject; #[pyclass] struct MyClass { num: i32, @@ -140,40 +193,44 @@ struct MyClass { #[pymethods] impl MyClass { - #[new] - fn new(obj: &PyRawObject, num: i32) { - obj.init({ - MyClass { - num, - } - }); + fn new(num: i32) -> Self { + MyClass { num } } } ``` -Rules for the `new` method: +If no method marked with `#[new]` is declared, object instances can only be +created from Rust, but not from Python. -* If no method marked with `#[new]` is declared, object instances can only be created - from Rust, but not from Python. -* The first parameter is the raw object and the custom `new` method must initialize the object - with an instance of the struct using the `init` method. The type of the object may be the type object of - a derived class declared in Python. -* The first parameter must have type `&PyRawObject`. -* For details on the parameter list, see the `Method arguments` section below. -* The return value must be `T` or `PyResult` where `T` is ignored, so it can - be just `()` as in the example above. +For arguments, see the `Method arguments` section below. +### Return type +Generally, `#[new]` method have to return `T: Into>` or +`PyResult where T: Into>`. -## Inheritance +For constructors that may fail, you should wrap the return type in a PyResult as well. +Consult the table below to determine which type your constructor should return: -By default, `PyObject` is used as the base class. To override this default, +| | **Cannot fail** | **May fail** | +|-----------------------------|-------------------------|-----------------------------------| +|**No inheritance** | `T` | `PyResult` | +|**Inheritance(T Inherits U)**| `(T, U)` | `PyResult<(T, U)>` | +|**Inheritance(General Case)**| `PyClassInitializer` | `PyResult>` | + +## Inheritance +By default, `PyAny` is used as the base class. To override this default, use the `extends` parameter for `pyclass` with the full path to the base class. -The `new` method of subclasses must call their parent's `new` method. -```rust,ignore +For convinience, `(T, U)` implements `Into>` where `U` is the +baseclass of `T`. +But for more deeply nested inheritance, you have to return `PyClassInitializer` +explicitly. + +```rust # use pyo3::prelude::*; -# use pyo3::PyRawObject; +use pyo3::PyClassShell; + #[pyclass] struct BaseClass { val1: usize, @@ -182,12 +239,12 @@ struct BaseClass { #[pymethods] impl BaseClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(BaseClass { val1: 10 }); + fn new() -> Self { + BaseClass { val1: 10 } } - pub fn method(&self) -> PyResult<()> { - Ok(()) + pub fn method(&self) -> PyResult { + Ok(self.val1) } } @@ -199,19 +256,70 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(SubClass { val2: 10 }); - BaseClass::new(obj); + fn new() -> (Self, BaseClass) { + (SubClass{ val2: 15}, BaseClass::new()) + } + + fn method2(self_: &PyClassShell) -> PyResult { + self_.get_super().method().map(|x| x * self_.val2) + } +} + +#[pyclass(extends=SubClass)] +struct SubSubClass { + val3: usize, +} + +#[pymethods] +impl SubSubClass { + #[new] + fn new() -> PyClassInitializer { + PyClassInitializer::from(SubClass::new()) + .add_subclass(SubSubClass{val3: 20}) } - fn method2(&self) -> PyResult<()> { - self.get_base().method() + fn method3(self_: &PyClassShell) -> PyResult { + let super_ = self_.get_super(); + SubClass::method2(super_).map(|x| x * self_.val3) } } + + +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# let subsub = pyo3::PyClassShell::new_ref(py, SubSubClass::new()).unwrap(); +# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000") ``` -The `ObjectProtocol` trait provides a `get_base()` method, which returns a reference -to the instance of the base struct. +To aceess super class, you can use either of these two ways. +- Use `self_: &PyClassShell` instead of `self`, and call `get_super()` +- `ObjectProtocol::get_base` +We recommend `PyClasShell` here, since it makes the context much clearer. + + +If `SubClass` does not provide baseclass initialization, compile fails. +```compile_fail +# use pyo3::prelude::*; +use pyo3::PyClassShell; + +#[pyclass] +struct BaseClass { + val1: usize, +} + +#[pyclass(extends=BaseClass)] +struct SubClass { + val2: usize, +} + +#[pymethods] +impl SubClass { + #[new] + fn new() -> Self { + SubClass{ val2: 15} + } +} +``` ## Object properties @@ -478,23 +586,20 @@ use pyo3::types::{PyDict, PyTuple}; #[pymethods] impl MyClass { #[new] - #[args(num="-1", debug="true")] - fn new(obj: &PyRawObject, num: i32, debug: bool) { - obj.init({ - MyClass { - num, - debug, - } - }); + #[args(num = "-1", debug = "true")] + fn new(num: i32, debug: bool) -> Self { + MyClass { num, debug } } + #[args( - num="10", - debug="true", - py_args="*", - name="\"Hello\"", - py_kwargs="**" + num = "10", + debug = "true", + py_args = "*", + name = "\"Hello\"", + py_kwargs = "**" )] - fn method(&mut self, + fn method( + &mut self, num: i32, debug: bool, name: &str, @@ -503,9 +608,12 @@ impl MyClass { ) -> PyResult { self.debug = debug; self.num = num; - Ok(format!("py_args={:?}, py_kwargs={:?}, name={}, num={}, debug={}", - py_args, py_kwargs, name, self.num, self.debug)) + Ok(format!( + "py_args={:?}, py_kwargs={:?}, name={}, num={}, debug={}", + py_args, py_kwargs, name, self.num, self.debug + )) } + fn make_change(&mut self, num: i32, debug: bool) -> PyResult { self.num = num; self.debug = debug; @@ -652,18 +760,16 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol Iterators can be defined using the [`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait. It includes two methods `__iter__` and `__next__`: - * `fn __iter__(slf: PyRefMut) -> PyResult>` - * `fn __next__(slf: PyRefMut) -> PyResult>>` + * `fn __iter__(slf: &mut PyClassShell) -> PyResult>` + * `fn __next__(slf: &mut PyClassShell) -> PyResult>>` Returning `Ok(None)` from `__next__` indicates that that there are no further items. Example: ```rust -extern crate pyo3; - use pyo3::prelude::*; -use pyo3::PyIterProtocol; +use pyo3::{PyIterProtocol, PyClassShell}; #[pyclass] struct MyIterator { @@ -672,10 +778,10 @@ struct MyIterator { #[pyproto] impl PyIterProtocol for MyIterator { - fn __iter__(slf: PyRefMut) -> PyResult> { + fn __iter__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.into()) } - fn __next__(mut slf: PyRefMut) -> PyResult> { + fn __next__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.iter.next()) } } diff --git a/guide/src/function.md b/guide/src/function.md index 26d16a0ef73..74925131733 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -108,8 +108,8 @@ impl MyClass { // the signature for the constructor is attached // to the struct definition instead. #[new] - fn new(obj: &PyRawObject, c: i32, d: &str) { - obj.init(Self {}); + fn new(c: i32, d: &str) -> Self { + Self {} } // the self argument should be written $self #[text_signature = "($self, e, f)"] diff --git a/guide/src/python_from_rust.md b/guide/src/python_from_rust.md index 5eaee1769d9..c20a3281853 100644 --- a/guide/src/python_from_rust.md +++ b/guide/src/python_from_rust.md @@ -35,7 +35,8 @@ Since `py_run!` can cause panic, we recommend you to use this macro only for tes your Python extensions quickly. ```rust -use pyo3::{PyObjectProtocol, prelude::*, py_run}; +use pyo3::prelude::*; +use pyo3::{PyClassShell, PyObjectProtocol, py_run}; # fn main() { #[pyclass] struct UserData { @@ -60,7 +61,7 @@ let userdata = UserData { id: 34, name: "Yu".to_string(), }; -let userdata = PyRef::new(py, userdata).unwrap(); +let userdata = PyClassShell::new_ref(py, userdata).unwrap(); let userdata_as_tuple = (34, "Yu"); py_run!(py, userdata userdata_as_tuple, r#" assert repr(userdata) == "User Yu(id: 34)" diff --git a/guide/src/rust_cpython.md b/guide/src/rust_cpython.md index dbaa841f6e0..ebb3fdaf604 100644 --- a/guide/src/rust_cpython.md +++ b/guide/src/rust_cpython.md @@ -26,7 +26,6 @@ py_class!(class MyClass |py| { ```rust use pyo3::prelude::*; -use pyo3::PyRawObject; #[pyclass] struct MyClass { @@ -36,12 +35,8 @@ struct MyClass { #[pymethods] impl MyClass { #[new] - fn new(obj: &PyRawObject, num: u32) { - obj.init({ - MyClass { - num, - } - }); + fn new(num: u32) -> Self { + MyClass { num } } fn half(&self) -> PyResult { @@ -74,7 +69,7 @@ impl PyList { fn new(py: Python) -> &PyList {...} - fn get_item(&self, index: isize) -> &PyObject {...} + fn get_item(&self, index: isize) -> &PyAny {...} } ``` diff --git a/pyo3-derive-backend/src/method.rs b/pyo3-derive-backend/src/method.rs index e8d25ca7591..ae0186caae2 100644 --- a/pyo3-derive-backend/src/method.rs +++ b/pyo3-derive-backend/src/method.rs @@ -29,7 +29,7 @@ pub enum FnType { FnCall, FnClass, FnStatic, - PySelf(syn::TypePath), + PySelf(syn::TypeReference), } #[derive(Clone, PartialEq, Debug)] @@ -78,7 +78,7 @@ impl<'a> FnSpec<'a> { ref pat, ref ty, .. }) => { // skip first argument (cls) - if (fn_type == FnType::FnClass || fn_type == FnType::FnNew) && !has_self { + if fn_type == FnType::FnClass && !has_self { has_self = true; continue; } @@ -116,11 +116,14 @@ impl<'a> FnSpec<'a> { if fn_type == FnType::Fn && !has_self { if arguments.is_empty() { - panic!("Static method needs #[staticmethod] attribute"); + return Err(syn::Error::new_spanned( + name, + "Static method needs #[staticmethod] attribute", + )); } let tp = match arguments.remove(0).ty { - syn::Type::Path(p) => replace_self(p), - _ => panic!("Invalid type as self"), + syn::Type::Reference(r) => replace_self(r)?, + x => return Err(syn::Error::new_spanned(x, "Invalid type as custom self")), }; fn_type = FnType::PySelf(tp); } @@ -510,15 +513,19 @@ fn parse_method_name_attribute( }) } -// Replace A with A<_> -fn replace_self(path: &syn::TypePath) -> syn::TypePath { +// Replace &A with &A<_> +fn replace_self(refn: &syn::TypeReference) -> syn::Result { fn infer(span: proc_macro2::Span) -> syn::GenericArgument { syn::GenericArgument::Type(syn::Type::Infer(syn::TypeInfer { underscore_token: syn::token::Underscore { spans: [span] }, })) } - let mut res = path.to_owned(); - for seg in &mut res.path.segments { + let mut res = refn.to_owned(); + let tp = match &mut *res.elem { + syn::Type::Path(p) => p, + _ => return Err(syn::Error::new_spanned(refn, "unsupported argument")), + }; + for seg in &mut tp.path.segments { if let syn::PathArguments::AngleBracketed(ref mut g) = seg.arguments { let mut args = syn::punctuated::Punctuated::new(); for arg in &g.args { @@ -539,5 +546,6 @@ fn replace_self(path: &syn::TypePath) -> syn::TypePath { g.args = args; } } - res + res.lifetime = None; + Ok(res) } diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 78118380b01..b7d490b3ab7 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -16,6 +16,7 @@ pub struct PyClassArgs { pub name: Option, pub flags: Vec, pub base: syn::TypePath, + pub has_extends: bool, pub module: Option, } @@ -39,8 +40,9 @@ impl Default for PyClassArgs { module: None, // We need the 0 as value for the constant we're later building using quote for when there // are no other flags - flags: vec![parse_quote! {0}], - base: parse_quote! {pyo3::types::PyAny}, + flags: vec![parse_quote! { 0 }], + base: parse_quote! { pyo3::types::PyAny }, + has_extends: false, } } } @@ -89,6 +91,7 @@ impl PyClassArgs { path: exp.path.clone(), qself: None, }; + self.has_extends = true; } _ => { return Err(syn::Error::new_spanned( @@ -127,10 +130,10 @@ impl PyClassArgs { let flag = exp.path.segments.first().unwrap().ident.to_string(); let path = match flag.as_str() { "gc" => { - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} + parse_quote! {pyo3::type_flags::GC} } "weakref" => { - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} + parse_quote! {pyo3::type_flags::WEAKREF} } "subclass" => { if cfg!(not(feature = "unsound-subclass")) { @@ -139,10 +142,10 @@ impl PyClassArgs { "You need to activate the `unsound-subclass` feature if you want to use subclassing", )); } - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_BASETYPE} + parse_quote! {pyo3::type_flags::BASETYPE} } "dict" => { - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} + parse_quote! {pyo3::type_flags::DICT} } _ => { return Err(syn::Error::new_spanned( @@ -268,7 +271,7 @@ fn impl_class( let extra = { if let Some(freelist) = &attr.freelist { quote! { - impl pyo3::freelist::PyObjectWithFreeList for #cls { + impl pyo3::freelist::PyClassWithFreeList for #cls { #[inline] fn get_free_list() -> &'static mut pyo3::freelist::FreeList<*mut pyo3::ffi::PyObject> { static mut FREELIST: *mut pyo3::freelist::FreeList<*mut pyo3::ffi::PyObject> = 0 as *mut _; @@ -286,7 +289,7 @@ fn impl_class( } } else { quote! { - impl pyo3::type_object::PyObjectAlloc for #cls {} + impl pyo3::pyclass::PyClassAlloc for #cls {} } } }; @@ -309,24 +312,25 @@ fn impl_class( let mut has_gc = false; for f in attr.flags.iter() { if let syn::Expr::Path(ref epath) = f { - if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} { + if epath.path == parse_quote! { pyo3::type_flags::WEAKREF } { has_weakref = true; - } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} { + } else if epath.path == parse_quote! { pyo3::type_flags::DICT } { has_dict = true; - } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} { + } else if epath.path == parse_quote! { pyo3::type_flags::GC } { has_gc = true; } } } + let weakref = if has_weakref { - quote! {std::mem::size_of::<*const pyo3::ffi::PyObject>()} + quote! { type WeakRef = pyo3::pyclass_slots::PyClassWeakRefSlot; } } else { - quote! {0} + quote! { type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; } }; let dict = if has_dict { - quote! {std::mem::size_of::<*const pyo3::ffi::PyObject>()} + quote! { type Dict = pyo3::pyclass_slots::PyClassDictSlot; } } else { - quote! {0} + quote! { type Dict = pyo3::pyclass_slots::PyClassDummySlot; } }; let module = if let Some(m) = &attr.module { quote! { Some(#m) } @@ -354,29 +358,36 @@ fn impl_class( let base = &attr.base; let flags = &attr.flags; + let extended = if attr.has_extends { + quote! { pyo3::type_flags::EXTENDED } + } else { + quote! { 0 } + }; + + // If #cls is not extended type, we allow Self->PyObject conversion + let into_pyobject = if !attr.has_extends { + quote! { + impl pyo3::IntoPy for #cls { + fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { + pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) + } + } + } + } else { + quote! {} + }; Ok(quote! { impl pyo3::type_object::PyTypeInfo for #cls { type Type = #cls; type BaseType = #base; + type ConcreteLayout = pyo3::pyclass::PyClassShell; + type Initializer = pyo3::pyclass_init::PyClassInitializer; const NAME: &'static str = #cls_name; const MODULE: Option<&'static str> = #module; const DESCRIPTION: &'static str = #doc; - const FLAGS: usize = #(#flags)|*; - - const SIZE: usize = { - Self::OFFSET as usize + - ::std::mem::size_of::<#cls>() + #weakref + #dict - }; - const OFFSET: isize = { - // round base_size up to next multiple of align - ( - (<#base as pyo3::type_object::PyTypeInfo>::SIZE + - ::std::mem::align_of::<#cls>() - 1) / - ::std::mem::align_of::<#cls>() * ::std::mem::align_of::<#cls>() - ) as isize - }; + const FLAGS: usize = #(#flags)|* | #extended; #[inline] unsafe fn type_object() -> &'static mut pyo3::ffi::PyTypeObject { @@ -385,12 +396,13 @@ fn impl_class( } } - impl pyo3::IntoPy for #cls { - fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { - pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) - } + impl pyo3::PyClass for #cls { + #dict + #weakref } + #into_pyobject + #inventory_impl #extra diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index ce051d29142..e7af4967de7 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -10,7 +10,6 @@ pub fn gen_py_method( meth_attrs: &mut Vec, ) -> syn::Result { check_generic(sig)?; - let spec = FnSpec::parse(sig, &mut *meth_attrs, true)?; Ok(match spec.tp { @@ -53,7 +52,7 @@ pub fn impl_wrap(cls: &syn::Type, spec: &FnSpec<'_>, noargs: bool) -> TokenStrea pub fn impl_wrap_pyslf( cls: &syn::Type, spec: &FnSpec<'_>, - self_ty: &syn::TypePath, + self_ty: &syn::TypeReference, noargs: bool, ) -> TokenStream { let names = get_arg_names(spec); @@ -151,9 +150,12 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { let name = &spec.name; let python_name = &spec.python_name; let names: Vec = get_arg_names(&spec); - let cb = quote! { #cls::#name(&_obj, #(#names),*) }; - - let body = impl_arg_params(spec, cb); + let cb = quote! { #cls::#name(#(#names),*) }; + let body = impl_arg_params_( + spec, + cb, + quote! { pyo3::derive_utils::IntoPyNewResult::into_pynew_result }, + ); quote! { #[allow(unused_mut)] @@ -167,25 +169,14 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#python_name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); - match pyo3::type_object::PyRawObject::new(_py, #cls::type_object(), _cls) { - Ok(_obj) => { - let _args = _py.from_borrowed_ptr::(_args); - let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - - #body - - match _result { - Ok(_) => pyo3::IntoPyPointer::into_ptr(_obj), - Err(e) => { - e.restore(_py); - ::std::ptr::null_mut() - } - } - } - Err(e) => { - e.restore(_py); - ::std::ptr::null_mut() - } + let _args = _py.from_borrowed_ptr::(_args); + let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); + + #body + + match _result.and_then(|init| pyo3::PyClassInitializer::from(init).create_shell(_py)) { + Ok(slf) => slf as _, + Err(e) => e.restore_and_null(_py), } } } @@ -377,11 +368,11 @@ fn bool_to_ident(condition: bool) -> syn::Ident { } } -pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream { +fn impl_arg_params_(spec: &FnSpec<'_>, body: TokenStream, into_result: TokenStream) -> TokenStream { if spec.args.is_empty() { return quote! { let _result = { - pyo3::derive_utils::IntoPyResult::into_py_result(#body) + #into_result (#body) }; }; } @@ -439,11 +430,19 @@ pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream { #(#param_conversion)* - pyo3::derive_utils::IntoPyResult::into_py_result(#body) + #into_result(#body) })(); } } +pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream { + impl_arg_params_( + spec, + body, + quote! { pyo3::derive_utils::IntoPyResult::into_py_result }, + ) +} + /// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument /// index and the index in option diverge when using py: Python fn impl_arg_param( diff --git a/src/class/iter.rs b/src/class/iter.rs index a528289dd67..802bd159e40 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -4,11 +4,8 @@ use crate::callback::{CallbackConverter, PyObjectCallbackConverter}; use crate::err::PyResult; -use crate::instance::PyRefMut; -use crate::type_object::PyTypeInfo; -use crate::IntoPyPointer; -use crate::Python; -use crate::{ffi, IntoPy, PyObject}; +use crate::{ffi, pyclass::PyClassShell, IntoPy, PyClass, PyObject}; +use crate::{IntoPyPointer, Python}; use std::ptr; /// Python Iterator Interface. @@ -16,15 +13,15 @@ use std::ptr; /// more information /// `https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_iter` #[allow(unused_variables)] -pub trait PyIterProtocol<'p>: PyTypeInfo + Sized { - fn __iter__(slf: PyRefMut<'p, Self>) -> Self::Result +pub trait PyIterProtocol<'p>: PyClass { + fn __iter__(slf: &mut PyClassShell) -> Self::Result where Self: PyIterIterProtocol<'p>, { unimplemented!() } - fn __next__(slf: PyRefMut<'p, Self>) -> Self::Result + fn __next__(slf: &mut PyClassShell) -> Self::Result where Self: PyIterNextProtocol<'p>, { diff --git a/src/class/macros.rs b/src/class/macros.rs index b849aad5604..7fbf2499be9 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -35,11 +35,11 @@ macro_rules! py_unary_pyref_func { where T: for<'p> $trait<'p>, { - use $crate::instance::PyRefMut; + use $crate::pyclass::PyClassShell; let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); - let res = $class::$f(PyRefMut::from_mut(slf)).into(); + let slf: &mut PyClassShell = &mut *(slf as *mut PyClassShell); + let res = $class::$f(slf).into(); $crate::callback::cb_convert($conv, py, res) } Some(wrap::<$class>) diff --git a/src/conversion.rs b/src/conversion.rs index 45c3a389331..d24e9f48ab5 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -3,7 +3,7 @@ //! Conversions between various states of rust and python types and their wrappers. use crate::err::{self, PyDowncastError, PyResult}; use crate::object::PyObject; -use crate::type_object::PyTypeInfo; +use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::types::PyAny; use crate::types::PyTuple; use crate::{ffi, gil, Py, Python}; @@ -393,23 +393,13 @@ where #[inline] unsafe fn try_from_unchecked>(value: V) -> &'v T { let value = value.into(); - let ptr = if T::OFFSET == 0 { - value as *const _ as *const u8 as *const T - } else { - (value.as_ptr() as *const u8).offset(T::OFFSET) as *const T - }; - &*ptr + T::ConcreteLayout::internal_ref_cast(value) } #[inline] unsafe fn try_from_mut_unchecked>(value: V) -> &'v mut T { let value = value.into(); - let ptr = if T::OFFSET == 0 { - value as *const _ as *mut u8 as *mut T - } else { - (value.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T - }; - &mut *ptr + T::ConcreteLayout::internal_mut_cast(value) } } @@ -461,10 +451,11 @@ where T: PyTypeInfo, { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_downcast(gil::register_owned(py, p))) + NonNull::new(ptr).map(|p| T::ConcreteLayout::internal_ref_cast(gil::register_owned(py, p))) } unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_downcast(gil::register_borrowed(py, p))) + NonNull::new(ptr) + .map(|p| T::ConcreteLayout::internal_ref_cast(gil::register_borrowed(py, p))) } } @@ -473,10 +464,11 @@ where T: PyTypeInfo, { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_mut_downcast(gil::register_owned(py, p))) + NonNull::new(ptr).map(|p| T::ConcreteLayout::internal_mut_cast(gil::register_owned(py, p))) } unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_mut_downcast(gil::register_borrowed(py, p))) + NonNull::new(ptr) + .map(|p| T::ConcreteLayout::internal_mut_cast(gil::register_borrowed(py, p))) } } diff --git a/src/derive_utils.rs b/src/derive_utils.rs index c1a9ac32f3d..a5e284b5f9a 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -8,10 +8,10 @@ use crate::err::PyResult; use crate::exceptions::TypeError; use crate::init_once; use crate::instance::PyNativeType; +use crate::pyclass::PyClass; +use crate::pyclass_init::PyClassInitializer; use crate::types::{PyAny, PyDict, PyModule, PyTuple}; -use crate::GILPool; -use crate::Python; -use crate::{ffi, IntoPy, PyObject}; +use crate::{ffi, GILPool, IntoPy, PyObject, Python}; use std::ptr; /// Description of a python parameter; used for `parse_args()`. @@ -181,3 +181,21 @@ impl> IntoPyResult for PyResult { self } } + +/// Variant of IntoPyResult for the specific case of #[new]. In the case of returning (Sub, Base) +/// from #[new], IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy. +pub trait IntoPyNewResult>> { + fn into_pynew_result(self) -> PyResult; +} + +impl>> IntoPyNewResult for I { + fn into_pynew_result(self) -> PyResult { + Ok(self) + } +} + +impl>> IntoPyNewResult for PyResult { + fn into_pynew_result(self) -> PyResult { + self + } +} diff --git a/src/err.rs b/src/err.rs index 3f653e36d41..3868ebd809c 100644 --- a/src/err.rs +++ b/src/err.rs @@ -326,6 +326,13 @@ impl PyErr { unsafe { ffi::PyErr_Restore(ptype.into_ptr(), pvalue, ptraceback.into_ptr()) } } + #[doc(hidden)] + /// Utility method for proc-macro code + pub fn restore_and_null(self, py: Python) -> *mut T { + self.restore(py); + std::ptr::null_mut() + } + /// Issue a warning message. /// May return a PyErr if warnings-as-errors is enabled. pub fn warn(py: Python, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> { diff --git a/src/ffi/code.rs b/src/ffi/code.rs index 310cb307c4b..05609269007 100644 --- a/src/ffi/code.rs +++ b/src/ffi/code.rs @@ -31,13 +31,6 @@ pub struct PyCodeObject { pub co_extra: *mut c_void, } -impl Default for PyCodeObject { - #[inline] - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} - /* Masks for co_flags */ pub const CO_OPTIMIZED: c_int = 0x0001; pub const CO_NEWLOCALS: c_int = 0x0002; diff --git a/src/ffi/complexobject.rs b/src/ffi/complexobject.rs index 024c93f956a..884d9eee89b 100644 --- a/src/ffi/complexobject.rs +++ b/src/ffi/complexobject.rs @@ -27,7 +27,6 @@ extern "C" { pub fn PyComplex_ImagAsDouble(op: *mut PyObject) -> c_double; } -#[cfg(not(Py_LIMITED_API))] #[repr(C)] #[derive(Copy, Clone)] pub struct Py_complex { @@ -35,11 +34,10 @@ pub struct Py_complex { pub imag: c_double, } -#[cfg(not(Py_LIMITED_API))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyComplexObject { - _ob_base: PyObject, + pub ob_base: PyObject, pub cval: Py_complex, } diff --git a/src/ffi/dictobject.rs b/src/ffi/dictobject.rs index 6982f8f54eb..1c8273af815 100644 --- a/src/ffi/dictobject.rs +++ b/src/ffi/dictobject.rs @@ -13,6 +13,22 @@ extern "C" { pub static mut PyDictValues_Type: PyTypeObject; } +#[repr(C)] +pub struct PyDictKeysObject { + _unused: [u8; 0], +} + +#[repr(C)] +#[derive(Debug)] +pub struct PyDictObject { + pub ob_base: PyObject, + pub ma_used: Py_ssize_t, + #[cfg(Py_3_6)] + pub ma_version_tag: u64, + pub ma_keys: *mut PyDictKeysObject, + pub ma_values: *mut *mut PyObject, +} + #[inline] pub unsafe fn PyDict_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) diff --git a/src/ffi/floatobject.rs b/src/ffi/floatobject.rs index f5d8edd30f1..7ea4f223795 100644 --- a/src/ffi/floatobject.rs +++ b/src/ffi/floatobject.rs @@ -1,7 +1,6 @@ use crate::ffi::object::*; use std::os::raw::{c_double, c_int}; -#[cfg(not(Py_LIMITED_API))] #[repr(C)] pub struct PyFloatObject { pub ob_base: PyObject, diff --git a/src/ffi/listobject.rs b/src/ffi/listobject.rs index c504ae4bbc1..9b8fa7e69d9 100644 --- a/src/ffi/listobject.rs +++ b/src/ffi/listobject.rs @@ -5,13 +5,7 @@ use std::os::raw::c_int; #[repr(C)] #[derive(Copy, Clone)] pub struct PyListObject { - #[cfg(py_sys_config = "Py_TRACE_REFS")] - pub _ob_next: *mut PyObject, - #[cfg(py_sys_config = "Py_TRACE_REFS")] - pub _ob_prev: *mut PyObject, - pub ob_refcnt: Py_ssize_t, - pub ob_type: *mut PyTypeObject, - pub ob_size: Py_ssize_t, + pub ob_base: PyVarObject, pub ob_item: *mut *mut PyObject, pub allocated: Py_ssize_t, } diff --git a/src/ffi/longobject.rs b/src/ffi/longobject.rs index 45bcebfd8ef..ba260b6b527 100644 --- a/src/ffi/longobject.rs +++ b/src/ffi/longobject.rs @@ -6,8 +6,10 @@ use std::os::raw::{ }; /// This is an opaque type in the python c api -#[repr(transparent)] -pub struct PyLongObject(*mut c_void); +#[repr(C)] +pub struct PyLongObject { + _unused: [u8; 0], +} #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 7fa6d1b5f0d..19d3bb86f8f 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,6 +1,6 @@ #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] #![cfg_attr(Py_LIMITED_API, allow(unused_imports))] -#![cfg_attr(feature="cargo-clippy", allow(clippy::inline_always))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] pub use self::bltinmodule::*; pub use self::boolobject::*; diff --git a/src/ffi/setobject.rs b/src/ffi/setobject.rs index 605e1a9d68d..9f6282801eb 100644 --- a/src/ffi/setobject.rs +++ b/src/ffi/setobject.rs @@ -1,5 +1,5 @@ use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::ffi::pyport::{Py_hash_t, Py_ssize_t}; use std::os::raw::c_int; #[cfg_attr(windows, link(name = "pythonXY"))] @@ -11,6 +11,28 @@ extern "C" { pub static mut PySetIter_Type: PyTypeObject; } +pub const PySet_MINSIZE: usize = 8; + +#[repr(C)] +#[derive(Debug)] +pub struct setentry { + pub key: *mut PyObject, + pub hash: Py_hash_t, +} + +#[repr(C)] +pub struct PySetObject { + pub ob_base: PyObject, + pub fill: Py_ssize_t, + pub used: Py_ssize_t, + pub mask: Py_ssize_t, + pub table: *mut setentry, + pub hash: Py_hash_t, + pub finger: Py_ssize_t, + pub smalltable: [setentry; PySet_MINSIZE], + pub weakreflist: *mut PyObject, +} + #[inline] #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_CheckExact")] pub unsafe fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int { diff --git a/src/ffi/sliceobject.rs b/src/ffi/sliceobject.rs index 9068a887203..259caa388dd 100644 --- a/src/ffi/sliceobject.rs +++ b/src/ffi/sliceobject.rs @@ -26,6 +26,14 @@ pub unsafe fn PySlice_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &mut PySlice_Type) as c_int } +#[repr(C)] +pub struct PySliceObject { + pub ob_base: PyObject, + pub start: *mut PyObject, + pub stop: *mut PyObject, + pub step: *mut PyObject, +} + #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { #[cfg_attr(PyPy, link_name = "PyPySlice_New")] diff --git a/src/ffi/tupleobject.rs b/src/ffi/tupleobject.rs index 191016a6e4f..3d46347cfd1 100644 --- a/src/ffi/tupleobject.rs +++ b/src/ffi/tupleobject.rs @@ -3,7 +3,6 @@ use crate::ffi::pyport::Py_ssize_t; use std::os::raw::c_int; #[repr(C)] -#[cfg(not(Py_LIMITED_API))] pub struct PyTupleObject { pub ob_base: PyVarObject, pub ob_item: [*mut PyObject; 1], diff --git a/src/freelist.rs b/src/freelist.rs index ec0c17781e0..cb7113eb3a1 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -3,7 +3,8 @@ //! Free allocation list use crate::ffi; -use crate::type_object::{pytype_drop, PyObjectAlloc, PyTypeInfo}; +use crate::pyclass::{tp_free_fallback, PyClassAlloc}; +use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::Python; use std::mem; use std::os::raw::c_void; @@ -11,7 +12,7 @@ use std::os::raw::c_void; /// Implementing this trait for custom class adds free allocation list to class. /// The performance improvement applies to types that are often created and deleted in a row, /// so that they can benefit from a freelist. -pub trait PyObjectWithFreeList: PyTypeInfo { +pub trait PyClassWithFreeList { fn get_free_list() -> &'static mut FreeList<*mut ffi::PyObject>; } @@ -67,43 +68,31 @@ impl FreeList { } } -impl PyObjectAlloc for T +impl PyClassAlloc for T where - T: PyObjectWithFreeList, + T: PyTypeInfo + PyClassWithFreeList, { - unsafe fn alloc(_py: Python) -> *mut ffi::PyObject { - if let Some(obj) = ::get_free_list().pop() { + unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { + if let Some(obj) = ::get_free_list().pop() { ffi::PyObject_Init(obj, ::type_object()); - obj + obj as _ } else { - ffi::PyType_GenericAlloc(::type_object(), 0) + crate::pyclass::default_alloc::() as _ } } - unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) { - pytype_drop::(py, obj); + unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { + (*self_).py_drop(py); + let obj = self_ as _; if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 { return; } - if let Some(obj) = ::get_free_list().insert(obj) { + if let Some(obj) = ::get_free_list().insert(obj) { match Self::type_object().tp_free { Some(free) => free(obj as *mut c_void), - None => { - let ty = ffi::Py_TYPE(obj); - if ffi::PyType_IS_GC(ty) != 0 { - ffi::PyObject_GC_Del(obj as *mut c_void); - } else { - ffi::PyObject_Free(obj as *mut c_void); - } - - // For heap types, PyType_GenericAlloc calls INCREF on the type objects, - // so we need to call DECREF here: - if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { - ffi::Py_DECREF(ty as *mut ffi::PyObject); - } - } + None => tp_free_fallback(obj), } } } diff --git a/src/instance.rs b/src/instance.rs index 100710279bb..5a6475e152c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,18 +1,16 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::err::{PyErr, PyResult}; use crate::gil; -use crate::instance; -use crate::internal_tricks::Unsendable; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; -use crate::type_object::PyTypeCreate; -use crate::type_object::{PyTypeInfo, PyTypeObject}; +use crate::pyclass::{PyClass, PyClassShell}; +use crate::pyclass_init::PyClassInitializer; +use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::types::PyAny; use crate::{ffi, IntoPy}; -use crate::{AsPyPointer, FromPyObject, FromPyPointer, IntoPyPointer, Python, ToPyObject}; +use crate::{AsPyPointer, FromPyObject, IntoPyPointer, Python, ToPyObject}; use std::marker::PhantomData; use std::mem; -use std::ops::{Deref, DerefMut}; use std::ptr::NonNull; /// Types that are built into the python interpreter. @@ -25,243 +23,6 @@ pub unsafe trait PyNativeType: Sized { } } -/// A special reference of type `T`. `PyRef` refers a instance of T, which exists in the Python -/// heap as a part of a Python object. -/// -/// We can't implement `AsPyPointer` or `ToPyObject` for `pyclass`es, because they're not Python -/// objects until copied to the Python heap. So, instead of treating `&pyclass`es as Python objects, -/// we need to use special reference types `PyRef` and `PyRefMut`. -/// -/// # Example -/// -/// ``` -/// use pyo3::prelude::*; -/// use pyo3::types::IntoPyDict; -/// #[pyclass] -/// struct Point { -/// x: i32, -/// y: i32, -/// } -/// #[pymethods] -/// impl Point { -/// fn length(&self) -> i32 { -/// self.x * self.y -/// } -/// } -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let obj = PyRef::new(gil.python(), Point { x: 3, y: 4 }).unwrap(); -/// let d = [("p", obj)].into_py_dict(py); -/// py.run("assert p.length() == 12", None, Some(d)).unwrap(); -/// ``` -#[derive(Debug)] -pub struct PyRef<'a, T: PyTypeInfo>(&'a T, Unsendable); - -#[allow(clippy::cast_ptr_alignment)] -fn ref_to_ptr(t: &T) -> *mut ffi::PyObject -where - T: PyTypeInfo, -{ - unsafe { (t as *const _ as *mut u8).offset(-T::OFFSET) as *mut _ } -} - -impl<'a, T: PyTypeInfo> PyRef<'a, T> { - pub(crate) fn from_ref(r: &'a T) -> Self { - PyRef(r, Unsendable::default()) - } -} - -impl<'p, T> PyRef<'p, T> -where - T: PyTypeInfo + PyTypeObject + PyTypeCreate, -{ - pub fn new(py: Python<'p>, value: T) -> PyResult> { - let obj = T::create(py)?; - obj.init(value); - unsafe { Self::from_owned_ptr_or_err(py, obj.into_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> AsPyPointer for PyRef<'a, T> { - fn as_ptr(&self) -> *mut ffi::PyObject { - ref_to_ptr(self.0) - } -} - -impl<'a, T: PyTypeInfo> ToPyObject for PyRef<'a, T> { - fn to_object(&self, py: Python) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> IntoPy for PyRef<'a, T> { - fn into_py(self, py: Python) -> PyObject { - self.to_object(py) - } -} - -impl<'a, T: PyTypeInfo> Deref for PyRef<'a, T> { - type Target = T; - fn deref(&self) -> &T { - self.0 - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for PyRef<'p, T> -where - T: PyTypeInfo, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_owned_ptr_or_opt(py, ptr).map(Self::from_ref) - } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_borrowed_ptr_or_opt(py, ptr).map(Self::from_ref) - } -} - -/// Mutable version of [`PyRef`](struct.PyRef.html). -/// # Example -/// ``` -/// use pyo3::prelude::*; -/// use pyo3::types::IntoPyDict; -/// #[pyclass] -/// struct Point { -/// x: i32, -/// y: i32, -/// } -/// #[pymethods] -/// impl Point { -/// fn length(&self) -> i32 { -/// self.x * self.y -/// } -/// } -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let mut obj = PyRefMut::new(gil.python(), Point { x: 3, y: 4 }).unwrap(); -/// let d = vec![("p", obj.to_object(py))].into_py_dict(py); -/// obj.x = 5; obj.y = 20; -/// py.run("assert p.length() == 100", None, Some(d)).unwrap(); -/// ``` -#[derive(Debug)] -pub struct PyRefMut<'a, T: PyTypeInfo>(&'a mut T, Unsendable); - -impl<'a, T: PyTypeInfo> PyRefMut<'a, T> { - pub(crate) fn from_mut(t: &'a mut T) -> Self { - PyRefMut(t, Unsendable::default()) - } -} - -impl<'p, T> PyRefMut<'p, T> -where - T: PyTypeInfo + PyTypeObject + PyTypeCreate, -{ - pub fn new(py: Python<'p>, value: T) -> PyResult> { - let obj = T::create(py)?; - obj.init(value); - unsafe { Self::from_owned_ptr_or_err(py, obj.into_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> AsPyPointer for PyRefMut<'a, T> { - fn as_ptr(&self) -> *mut ffi::PyObject { - ref_to_ptr(self.0) - } -} - -impl<'a, T: PyTypeInfo> ToPyObject for PyRefMut<'a, T> { - fn to_object(&self, py: Python) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> IntoPy for PyRefMut<'a, T> { - fn into_py(self, py: Python) -> PyObject { - self.to_object(py) - } -} - -impl<'a, T: PyTypeInfo> Deref for PyRefMut<'a, T> { - type Target = T; - fn deref(&self) -> &T { - self.0 - } -} - -impl<'a, T: PyTypeInfo> DerefMut for PyRefMut<'a, T> { - fn deref_mut(&mut self) -> &mut T { - self.0 - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for PyRefMut<'p, T> -where - T: PyTypeInfo, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_owned_ptr_or_opt(py, ptr).map(Self::from_mut) - } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_borrowed_ptr_or_opt(py, ptr).map(Self::from_mut) - } -} - -/// Trait implements object reference extraction from python managed pointer. -pub trait AsPyRef: Sized { - /// Return reference to object. - fn as_ref(&self, py: Python) -> PyRef; - - /// Return mutable reference to object. - fn as_mut(&mut self, py: Python) -> PyRefMut; - - /// Acquire python gil and call closure with object reference. - fn with(&self, f: F) -> R - where - F: FnOnce(Python, PyRef) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - f(py, self.as_ref(py)) - } - - /// Acquire python gil and call closure with mutable object reference. - fn with_mut(&mut self, f: F) -> R - where - F: FnOnce(Python, PyRefMut) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - f(py, self.as_mut(py)) - } - - fn into_py(self, f: F) -> R - where - Self: IntoPyPointer, - F: FnOnce(Python, PyRef) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - let result = f(py, self.as_ref(py)); - py.xdecref(self); - result - } - - fn into_mut_py(mut self, f: F) -> R - where - Self: IntoPyPointer, - F: FnOnce(Python, PyRefMut) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - let result = f(py, self.as_mut(py)); - py.xdecref(self); - result - } -} - /// Safe wrapper around unsafe `*mut ffi::PyObject` pointer with specified type information. /// /// `Py` is thread-safe, because any python related operations require a Python<'p> token. @@ -275,14 +36,15 @@ unsafe impl Sync for Py {} impl Py { /// Create new instance of T and move it under python management - pub fn new(py: Python, value: T) -> PyResult> + pub fn new(py: Python, value: impl Into>) -> PyResult> where - T: PyTypeCreate, + T: PyClass, + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, { - let ob = T::create(py)?; - ob.init(value); - - let ob = unsafe { Py::from_owned_ptr(ob.into_ptr()) }; + let initializer = value.into(); + let obj = unsafe { initializer.create_shell(py)? }; + let ob = unsafe { Py::from_owned_ptr(obj as _) }; Ok(ob) } @@ -357,47 +119,15 @@ impl Py { } } -/// Specialization workaround -trait AsPyRefDispatch: AsPyPointer { - fn as_ref_dispatch(&self, _py: Python) -> &T; - fn as_mut_dispatch(&mut self, _py: Python) -> &mut T; -} - -impl AsPyRefDispatch for Py { - default fn as_ref_dispatch(&self, _py: Python) -> &T { - unsafe { - let ptr = (self.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - ptr.as_ref().expect("Py has a null pointer") - } - } - default fn as_mut_dispatch(&mut self, _py: Python) -> &mut T { - unsafe { - let ptr = (self.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - ptr.as_mut().expect("Py has a null pointer") - } - } -} - -impl AsPyRefDispatch for Py { - fn as_ref_dispatch(&self, _py: Python) -> &T { - unsafe { &*(self as *const instance::Py as *const T) } - } - fn as_mut_dispatch(&mut self, _py: Python) -> &mut T { - unsafe { &mut *(self as *mut _ as *mut T) } - } +pub trait AsPyRef: Sized { + /// Return reference to object. + fn as_ref(&self, py: Python) -> &T; } -impl AsPyRef for Py -where - T: PyTypeInfo, -{ - #[inline] - fn as_ref(&self, py: Python) -> PyRef { - PyRef::from_ref(self.as_ref_dispatch(py)) - } - #[inline] - fn as_mut(&mut self, py: Python) -> PyRefMut { - PyRefMut::from_mut(self.as_mut_dispatch(py)) +impl AsPyRef for Py { + fn as_ref(&self, _py: Python) -> &T { + let any = self as *const Py as *const PyAny; + unsafe { T::ConcreteLayout::internal_ref_cast(&*any) } } } @@ -412,8 +142,8 @@ impl IntoPy for Py { /// Converts `Py` instance -> PyObject. /// Consumes `self` without calling `Py_DECREF()` #[inline] - fn into_py(self, py: Python) -> PyObject { - unsafe { PyObject::from_owned_ptr(py, self.into_ptr()) } + fn into_py(self, _py: Python) -> PyObject { + unsafe { PyObject::from_not_null(self.into_non_null()) } } } @@ -430,9 +160,25 @@ impl IntoPyPointer for Py { #[inline] #[must_use] fn into_ptr(self) -> *mut ffi::PyObject { - let ptr = self.0.as_ptr(); - std::mem::forget(self); - ptr + self.into_non_null().as_ptr() + } +} + +impl<'a, T> std::convert::From<&PyClassShell> for Py +where + T: PyClass, +{ + fn from(shell: &PyClassShell) -> Self { + unsafe { Py::from_borrowed_ptr(shell.as_ptr()) } + } +} + +impl<'a, T> std::convert::From<&mut PyClassShell> for Py +where + T: PyClass, +{ + fn from(shell: &mut PyClassShell) -> Self { + unsafe { Py::from_borrowed_ptr(shell.as_ptr()) } } } @@ -459,24 +205,6 @@ impl std::convert::From> for PyObject { } } -impl<'a, T> std::convert::From> for Py -where - T: PyTypeInfo, -{ - fn from(ob: PyRef<'a, T>) -> Self { - unsafe { Py::from_borrowed_ptr(ob.as_ptr()) } - } -} - -impl<'a, T> std::convert::From> for Py -where - T: PyTypeInfo, -{ - fn from(ob: PyRefMut<'a, T>) -> Self { - unsafe { Py::from_borrowed_ptr(ob.as_ptr()) } - } -} - impl<'a, T> std::convert::From<&'a T> for PyObject where T: AsPyPointer, diff --git a/src/internal_tricks.rs b/src/internal_tricks.rs index 1cf8a56a68f..1d6a5eb9f27 100644 --- a/src/internal_tricks.rs +++ b/src/internal_tricks.rs @@ -4,3 +4,22 @@ use std::rc::Rc; /// A marker type that makes the type !Send. /// Temporal hack until https://github.com/rust-lang/rust/issues/13231 is resolved. pub(crate) type Unsendable = PhantomData>; + +pub struct PrivateMarker; + +macro_rules! private_decl { + () => { + /// This trait is private to implement; this method exists to make it + /// impossible to implement outside the crate. + fn __private__(&self) -> crate::internal_tricks::PrivateMarker; + } +} + +macro_rules! private_impl { + () => { + #[doc(hidden)] + fn __private__(&self) -> crate::internal_tricks::PrivateMarker { + crate::internal_tricks::PrivateMarker + } + } +} diff --git a/src/lib.rs b/src/lib.rs index dc1b2c34963..0367af54cc4 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,11 +124,13 @@ pub use crate::conversion::{ }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyErrValue, PyResult}; pub use crate::gil::{init_once, GILGuard, GILPool}; -pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType, PyRef, PyRefMut}; +pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; +pub use crate::pyclass::{PyClass, PyClassShell}; +pub use crate::pyclass_init::PyClassInitializer; pub use crate::python::{prepare_freethreaded_python, Python}; -pub use crate::type_object::{PyObjectAlloc, PyRawObject, PyTypeInfo}; +pub use crate::type_object::{type_flags, PyTypeInfo}; // Re-exported for wrap_function #[doc(hidden)] @@ -146,11 +148,6 @@ pub use libc; #[doc(hidden)] pub use unindent; -/// Raw ffi declarations for the c interface of python -#[allow(clippy::unknown_clippy_lints)] -#[allow(clippy::missing_safety_doc)] -pub mod ffi; - pub mod buffer; #[doc(hidden)] pub mod callback; @@ -160,14 +157,22 @@ mod conversion; pub mod derive_utils; mod err; pub mod exceptions; +/// Raw ffi declarations for the c interface of python +#[allow(clippy::unknown_clippy_lints)] +#[allow(clippy::missing_safety_doc)] +pub mod ffi; pub mod freelist; mod gil; mod instance; +#[macro_use] mod internal_tricks; pub mod marshal; mod object; mod objectprotocol; pub mod prelude; +pub mod pyclass; +pub mod pyclass_init; +pub mod pyclass_slots; mod python; pub mod type_object; pub mod types; @@ -216,7 +221,7 @@ macro_rules! wrap_pymodule { /// /// # Example /// ``` -/// use pyo3::{prelude::*, py_run}; +/// use pyo3::{prelude::*, py_run, PyClassShell}; /// #[pyclass] /// #[derive(Debug)] /// struct Time { @@ -239,7 +244,7 @@ macro_rules! wrap_pymodule { /// } /// let gil = Python::acquire_gil(); /// let py = gil.python(); -/// let time = PyRef::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap(); +/// let time = PyClassShell::new_ref(py, Time {hour: 8, minute: 43, second: 16}).unwrap(); /// let time_as_tuple = (8, 43, 16); /// py_run!(py, time time_as_tuple, r#" /// assert time.hour == 8 diff --git a/src/object.rs b/src/object.rs index 128ef8377bc..c465a8c84a3 100644 --- a/src/object.rs +++ b/src/object.rs @@ -3,11 +3,9 @@ use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::ffi; use crate::gil; -use crate::instance::{AsPyRef, PyNativeType, PyRef, PyRefMut}; +use crate::instance::{AsPyRef, PyNativeType}; use crate::types::{PyAny, PyDict, PyTuple}; -use crate::AsPyPointer; -use crate::Py; -use crate::Python; +use crate::{AsPyPointer, Py, Python}; use crate::{FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject}; use std::ptr::NonNull; @@ -164,7 +162,7 @@ impl PyObject { where D: FromPyObject<'p>, { - FromPyObject::extract(self.as_ref(py).into()) + FromPyObject::extract(self.as_ref(py)) } /// Retrieves an attribute value. @@ -253,13 +251,8 @@ impl PyObject { } impl AsPyRef for PyObject { - #[inline] - fn as_ref(&self, _py: Python) -> PyRef { - unsafe { PyRef::from_ref(&*(self as *const _ as *const PyAny)) } - } - #[inline] - fn as_mut(&mut self, _py: Python) -> PyRefMut { - unsafe { PyRefMut::from_mut(&mut *(self as *mut _ as *mut PyAny)) } + fn as_ref(&self, _py: Python) -> &PyAny { + unsafe { &*(self as *const _ as *const PyAny) } } } diff --git a/src/prelude.rs b/src/prelude.rs index 5e927446069..4e98df7cfe5 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,14 +12,13 @@ pub use crate::err::{PyErr, PyResult}; pub use crate::gil::GILGuard; -pub use crate::instance::{AsPyRef, Py, PyRef, PyRefMut}; +pub use crate::instance::{AsPyRef, Py}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; +pub use crate::pyclass_init::PyClassInitializer; pub use crate::python::Python; pub use crate::{FromPy, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, ToPyObject}; // This is only part of the prelude because we need it for the pymodule function pub use crate::types::PyModule; -// This is required for the constructor -pub use crate::PyRawObject; pub use pyo3cls::pymodule; pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto}; diff --git a/src/pyclass.rs b/src/pyclass.rs new file mode 100644 index 00000000000..715cf0dde0e --- /dev/null +++ b/src/pyclass.rs @@ -0,0 +1,500 @@ +//! Traits and structs for `#[pyclass]`. +use crate::class::methods::{PyMethodDefType, PyMethodsProtocol}; +use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject}; +use crate::pyclass_init::PyClassInitializer; +use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; +use crate::type_object::{type_flags, PyObjectLayout, PyObjectSizedLayout, PyTypeObject}; +use crate::types::PyAny; +use crate::{class, ffi, gil, PyErr, PyObject, PyResult, PyTypeInfo, Python}; +use std::ffi::CString; +use std::mem::ManuallyDrop; +use std::os::raw::c_void; +use std::ptr::{self, NonNull}; + +#[inline] +pub(crate) unsafe fn default_alloc() -> *mut ffi::PyObject { + let tp_ptr = T::type_object(); + if T::FLAGS & type_flags::EXTENDED != 0 + && ::ConcreteLayout::IS_NATIVE_TYPE + { + let base_tp_ptr = ::type_object(); + if let Some(base_new) = (*base_tp_ptr).tp_new { + return base_new(tp_ptr, ptr::null_mut(), ptr::null_mut()); + } + } + let alloc = (*tp_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); + alloc(tp_ptr, 0) +} + +/// This trait enables custom alloc/dealloc implementations for `T: PyClass`. +pub trait PyClassAlloc: PyTypeInfo + Sized { + /// Allocate the actual field for `#[pyclass]`. + /// + /// # Safety + /// This function must return a valid pointer to the Python heap. + unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { + default_alloc::() as _ + } + + /// Deallocate `#[pyclass]` on the Python heap. + /// + /// # Safety + /// `self_` must be a valid pointer to the Python heap. + unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { + (*self_).py_drop(py); + let obj = self_ as _; + if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 { + return; + } + + match Self::type_object().tp_free { + Some(free) => free(obj as *mut c_void), + None => tp_free_fallback(obj), + } + } +} + +#[doc(hidden)] +pub unsafe fn tp_free_fallback(obj: *mut ffi::PyObject) { + let ty = ffi::Py_TYPE(obj); + if ffi::PyType_IS_GC(ty) != 0 { + ffi::PyObject_GC_Del(obj as *mut c_void); + } else { + ffi::PyObject_Free(obj as *mut c_void); + } + + // For heap types, PyType_GenericAlloc calls INCREF on the type objects, + // so we need to call DECREF here: + if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { + ffi::Py_DECREF(ty as *mut ffi::PyObject); + } +} + +/// If `PyClass` is implemented for `T`, then we can use `T` in the Python world, +/// via `PyClassShell`. +/// +/// The `#[pyclass]` attribute automatically implements this trait for your Rust struct, +/// so you don't have to use this trait directly. +pub trait PyClass: + PyTypeInfo> + Sized + PyClassAlloc + PyMethodsProtocol +{ + type Dict: PyClassDict; + type WeakRef: PyClassWeakRef; +} + +unsafe impl PyTypeObject for T +where + T: PyClass, +{ + fn init_type() -> NonNull { + ::init_type(); + let type_object = unsafe { ::type_object() }; + + if (type_object.tp_flags & ffi::Py_TPFLAGS_READY) == 0 { + // automatically initialize the class on-demand + let gil = Python::acquire_gil(); + let py = gil.python(); + + initialize_type::(py, ::MODULE).unwrap_or_else(|e| { + e.print(py); + panic!("An error occurred while initializing class {}", Self::NAME) + }); + } + + unsafe { NonNull::new_unchecked(type_object) } + } +} + +/// `PyClassShell` represents the concrete layout of `T: PyClass` when it is converted +/// to a Python class. +/// +/// You can use it to test your `#[pyclass]` correctly works. +/// +/// ``` +/// # use pyo3::prelude::*; +/// # use pyo3::{py_run, PyClassShell}; +/// #[pyclass] +/// struct Book { +/// #[pyo3(get)] +/// name: &'static str, +/// author: &'static str, +/// } +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// let book = Book { +/// name: "The Man in the High Castle", +/// author: "Philip Kindred Dick", +/// }; +/// let book_shell = PyClassShell::new_ref(py, book).unwrap(); +/// py_run!(py, book_shell, "assert book_shell.name[-6:] == 'Castle'"); +/// ``` +#[repr(C)] +pub struct PyClassShell { + ob_base: ::ConcreteLayout, + pyclass: ManuallyDrop, + dict: T::Dict, + weakref: T::WeakRef, +} + +impl PyClassShell { + /// Make new `PyClassShell` on the Python heap and returns the reference of it. + pub fn new_ref(py: Python, value: impl Into>) -> PyResult<&Self> + where + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, + { + unsafe { + let initializer = value.into(); + let self_ = initializer.create_shell(py)?; + FromPyPointer::from_owned_ptr_or_err(py, self_ as _) + } + } + + /// Make new `PyClassShell` on the Python heap and returns the mutable reference of it. + pub fn new_mut(py: Python, value: impl Into>) -> PyResult<&mut Self> + where + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, + { + unsafe { + let initializer = value.into(); + let self_ = initializer.create_shell(py)?; + FromPyPointer::from_owned_ptr_or_err(py, self_ as _) + } + } + + /// Get the reference of base object. + pub fn get_super(&self) -> &::ConcreteLayout { + &self.ob_base + } + + /// Get the mutable reference of base object. + pub fn get_super_mut(&mut self) -> &mut ::ConcreteLayout { + &mut self.ob_base + } + + pub(crate) unsafe fn new(py: Python) -> PyResult<*mut Self> + where + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, + { + T::init_type(); + let base = T::alloc(py); + if base.is_null() { + return Err(PyErr::fetch(py)); + } + let self_ = base as *mut Self; + (*self_).dict = T::Dict::new(); + (*self_).weakref = T::WeakRef::new(); + Ok(self_) + } +} + +impl PyObjectLayout for PyClassShell { + const IS_NATIVE_TYPE: bool = false; + fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { + Some(&mut self.ob_base) + } + unsafe fn internal_ref_cast(obj: &PyAny) -> &T { + let shell = obj.as_ptr() as *const Self; + &(*shell).pyclass + } + unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { + let shell = obj.as_ptr() as *const _ as *mut Self; + &mut (*shell).pyclass + } + unsafe fn py_drop(&mut self, py: Python) { + ManuallyDrop::drop(&mut self.pyclass); + self.dict.clear_dict(py); + self.weakref.clear_weakrefs(self.as_ptr(), py); + self.ob_base.py_drop(py); + } + unsafe fn py_init(&mut self, value: T) { + self.pyclass = ManuallyDrop::new(value); + } +} + +impl PyObjectSizedLayout for PyClassShell {} + +impl AsPyPointer for PyClassShell { + fn as_ptr(&self) -> *mut ffi::PyObject { + (self as *const _) as *mut _ + } +} + +impl std::ops::Deref for PyClassShell { + type Target = T; + fn deref(&self) -> &T { + self.pyclass.deref() + } +} + +impl std::ops::DerefMut for PyClassShell { + fn deref_mut(&mut self) -> &mut T { + self.pyclass.deref_mut() + } +} + +impl ToPyObject for &PyClassShell { + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } + } +} + +impl ToPyObject for &mut PyClassShell { + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } + } +} + +unsafe impl<'p, T> FromPyPointer<'p> for &'p PyClassShell +where + T: PyClass, +{ + unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr).map(|p| &*(gil::register_owned(py, p).as_ptr() as *const PyClassShell)) + } + unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr) + .map(|p| &*(gil::register_borrowed(py, p).as_ptr() as *const PyClassShell)) + } +} + +unsafe impl<'p, T> FromPyPointer<'p> for &'p mut PyClassShell +where + T: PyClass, +{ + unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr).map(|p| { + &mut *(gil::register_owned(py, p).as_ptr() as *const _ as *mut PyClassShell) + }) + } + unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr).map(|p| { + &mut *(gil::register_borrowed(py, p).as_ptr() as *const _ as *mut PyClassShell) + }) + } +} + +/// Register `T: PyClass` to Python interpreter. +#[cfg(not(Py_LIMITED_API))] +pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> +where + T: PyClass, +{ + let type_object: &mut ffi::PyTypeObject = unsafe { T::type_object() }; + let base_type_object: &mut ffi::PyTypeObject = + unsafe { ::type_object() }; + + // PyPy will segfault if passed only a nul terminator as `tp_doc`. + // ptr::null() is OK though. + if T::DESCRIPTION == "\0" { + type_object.tp_doc = ptr::null(); + } else { + type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _; + }; + + type_object.tp_base = base_type_object; + + let name = match module_name { + Some(module_name) => format!("{}.{}", module_name, T::NAME), + None => T::NAME.to_string(), + }; + let name = CString::new(name).expect("Module name/type name must not contain NUL byte"); + type_object.tp_name = name.into_raw(); + + // dealloc + unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) + where + T: PyClassAlloc, + { + let py = Python::assume_gil_acquired(); + let _pool = gil::GILPool::new_no_pointers(py); + ::dealloc(py, (obj as *mut T::ConcreteLayout) as _) + } + type_object.tp_dealloc = Some(tp_dealloc_callback::); + + // type size + type_object.tp_basicsize = std::mem::size_of::() as ffi::Py_ssize_t; + + let mut offset = type_object.tp_basicsize; + + // __dict__ support + if let Some(dict_offset) = T::Dict::OFFSET { + offset += dict_offset as ffi::Py_ssize_t; + type_object.tp_dictoffset = offset; + } + + // weakref support + if let Some(weakref_offset) = T::WeakRef::OFFSET { + offset += weakref_offset as ffi::Py_ssize_t; + type_object.tp_weaklistoffset = offset; + } + + // GC support + ::update_type_object(type_object); + + // descriptor protocol + ::tp_as_descr(type_object); + + // iterator methods + ::tp_as_iter(type_object); + + // basic methods + ::tp_as_object(type_object); + + fn to_ptr(value: Option) -> *mut T { + value + .map(|v| Box::into_raw(Box::new(v))) + .unwrap_or_else(ptr::null_mut) + } + + // number methods + type_object.tp_as_number = to_ptr(::tp_as_number()); + // mapping methods + type_object.tp_as_mapping = + to_ptr(::tp_as_mapping()); + // sequence methods + type_object.tp_as_sequence = + to_ptr(::tp_as_sequence()); + // async methods + type_object.tp_as_async = to_ptr(::tp_as_async()); + // buffer protocol + type_object.tp_as_buffer = to_ptr(::tp_as_buffer()); + + // normal methods + let (new, call, mut methods) = py_class_method_defs::(); + if !methods.is_empty() { + methods.push(ffi::PyMethodDef_INIT); + type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as *mut _; + } + + // __new__ method + type_object.tp_new = new; + // __call__ method + type_object.tp_call = call; + + // properties + let mut props = py_class_properties::(); + + if T::Dict::OFFSET.is_some() { + props.push(ffi::PyGetSetDef_DICT); + } + if !props.is_empty() { + props.push(ffi::PyGetSetDef_INIT); + type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as *mut _; + } + + // set type flags + py_class_flags::(type_object); + + // register type object + unsafe { + if ffi::PyType_Ready(type_object) == 0 { + Ok(type_object as *mut ffi::PyTypeObject) + } else { + PyErr::fetch(py).into() + } + } +} + +fn py_class_flags(type_object: &mut ffi::PyTypeObject) { + if type_object.tp_traverse != None + || type_object.tp_clear != None + || T::FLAGS & type_flags::GC != 0 + { + type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; + } else { + type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; + } + if T::FLAGS & type_flags::BASETYPE != 0 { + type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE; + } +} + +fn py_class_method_defs() -> ( + Option, + Option, + Vec, +) { + let mut defs = Vec::new(); + let mut call = None; + let mut new = None; + + for def in T::py_methods() { + match *def { + PyMethodDefType::New(ref def) => { + if let class::methods::PyMethodType::PyNewFunc(meth) = def.ml_meth { + new = Some(meth) + } + } + PyMethodDefType::Call(ref def) => { + if let class::methods::PyMethodType::PyCFunctionWithKeywords(meth) = def.ml_meth { + call = Some(meth) + } else { + panic!("Method type is not supoorted by tp_call slot") + } + } + PyMethodDefType::Method(ref def) + | PyMethodDefType::Class(ref def) + | PyMethodDefType::Static(ref def) => { + defs.push(def.as_method_def()); + } + _ => (), + } + } + + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + + py_class_async_methods::(&mut defs); + + (new, call, defs) +} + +fn py_class_async_methods(defs: &mut Vec) { + for def in ::methods() { + defs.push(def.as_method_def()); + } +} + +fn py_class_properties() -> Vec { + let mut defs = std::collections::HashMap::new(); + + for def in T::py_methods() { + match *def { + PyMethodDefType::Getter(ref getter) => { + let name = getter.name.to_string(); + if !defs.contains_key(&name) { + let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); + } + let def = defs.get_mut(&name).expect("Failed to call get_mut"); + getter.copy_to(def); + } + PyMethodDefType::Setter(ref setter) => { + let name = setter.name.to_string(); + if !defs.contains_key(&name) { + let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); + } + let def = defs.get_mut(&name).expect("Failed to call get_mut"); + setter.copy_to(def); + } + _ => (), + } + } + + defs.values().cloned().collect() +} diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs new file mode 100644 index 00000000000..bc2904da177 --- /dev/null +++ b/src/pyclass_init.rs @@ -0,0 +1,162 @@ +//! Initialization utilities for `#[pyclass]`. +use crate::pyclass::{PyClass, PyClassShell}; +use crate::type_object::{PyObjectLayout, PyObjectSizedLayout, PyTypeInfo}; +use crate::{PyResult, Python}; +use std::marker::PhantomData; + +/// Initializer for Python types. +/// +/// This trait is intended to use internally for distinguishing `#[pyclass]` and +/// Python native types. +pub trait PyObjectInit: Sized { + fn init_class(self, shell: &mut T::ConcreteLayout); + private_decl! {} +} + +/// Initializer for Python native type, like `PyDict`. +pub struct PyNativeTypeInitializer(PhantomData); + +impl PyObjectInit for PyNativeTypeInitializer { + fn init_class(self, _shell: &mut T::ConcreteLayout) {} + private_impl! {} +} + +/// Initializer for our `#[pyclass]` system. +/// +/// You can use this type to initalize complicatedly nested `#[pyclass]`. +/// +/// # Example +/// +/// ``` +/// # use pyo3::prelude::*; +/// # use pyo3::py_run; +/// #[pyclass] +/// struct BaseClass { +/// #[pyo3(get)] +/// basename: &'static str, +/// } +/// #[pyclass(extends=BaseClass)] +/// struct SubClass { +/// #[pyo3(get)] +/// subname: &'static str, +/// } +/// #[pyclass(extends=SubClass)] +/// struct SubSubClass { +/// #[pyo3(get)] +/// subsubname: &'static str, +/// } +/// +/// #[pymethods] +/// impl SubSubClass { +/// #[new] +/// fn new() -> PyClassInitializer { +/// PyClassInitializer::from(BaseClass { basename: "base" }) +/// .add_subclass(SubClass { subname: "sub" }) +/// .add_subclass(SubSubClass { subsubname: "subsub" }) +/// } +/// } +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// let typeobj = py.get_type::(); +/// let inst = typeobj.call((), None).unwrap(); +/// py_run!(py, inst, r#" +/// assert inst.basename == 'base' +/// assert inst.subname == 'sub' +/// assert inst.subsubname == 'subsub'"#); +/// ``` +pub struct PyClassInitializer { + init: T, + super_init: ::Initializer, +} + +impl PyClassInitializer { + /// Constract new initialzer from value `T` and base class' initializer. + /// + /// We recommend to mainly use `add_subclass`, instead of directly call `new`. + pub fn new(init: T, super_init: ::Initializer) -> Self { + Self { init, super_init } + } + + /// Constructs a new initializer from base class' initializer. + /// + /// # Example + /// ``` + /// # use pyo3::prelude::*; + /// #[pyclass] + /// struct BaseClass { + /// value: u32, + /// } + /// + /// impl BaseClass { + /// fn new(value: i32) -> PyResult { + /// Ok(Self { + /// value: std::convert::TryFrom::try_from(value)?, + /// }) + /// } + /// } + /// #[pyclass(extends=BaseClass)] + /// struct SubClass {} + /// + /// #[pymethods] + /// impl SubClass { + /// #[new] + /// fn new(value: i32) -> PyResult> { + /// let base_init = PyClassInitializer::from(BaseClass::new(value)?); + /// Ok(base_init.add_subclass(SubClass {})) + /// } + /// } + /// ``` + pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer + where + S: PyClass + PyTypeInfo, + S::BaseType: PyTypeInfo, + { + PyClassInitializer::new(subclass_value, self) + } + + #[doc(hidden)] + pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell> + where + T: PyClass, + ::ConcreteLayout: PyObjectSizedLayout, + { + let shell = PyClassShell::new(py)?; + self.init_class(&mut *shell); + Ok(shell) + } +} + +impl PyObjectInit for PyClassInitializer { + fn init_class(self, obj: &mut T::ConcreteLayout) { + let Self { init, super_init } = self; + unsafe { + obj.py_init(init); + } + if let Some(super_obj) = obj.get_super_or() { + super_init.init_class(super_obj); + } + } + private_impl! {} +} + +impl From for PyClassInitializer +where + T: PyClass, + T::BaseType: PyTypeInfo>, +{ + fn from(value: T) -> PyClassInitializer { + Self::new(value, PyNativeTypeInitializer(PhantomData)) + } +} + +impl From<(S, B)> for PyClassInitializer +where + S: PyClass + PyTypeInfo, + B: PyClass + PyTypeInfo>, + B::BaseType: PyTypeInfo>, +{ + fn from(sub_and_base: (S, B)) -> PyClassInitializer { + let (sub, base) = sub_and_base; + PyClassInitializer::from(base).add_subclass(sub) + } +} diff --git a/src/pyclass_slots.rs b/src/pyclass_slots.rs new file mode 100644 index 00000000000..539e630e3eb --- /dev/null +++ b/src/pyclass_slots.rs @@ -0,0 +1,76 @@ +//! This module contains additional fields for `#[pyclass]`.. +//! Mainly used by our proc-macro codes. +use crate::{ffi, Python}; + +const POINTER_SIZE: isize = std::mem::size_of::<*mut ffi::PyObject>() as _; + +/// Represents `__dict__` field for `#[pyclass]`. +pub trait PyClassDict { + const OFFSET: Option = None; + fn new() -> Self; + unsafe fn clear_dict(&mut self, _py: Python) {} + private_decl! {} +} + +/// Represents `__weakref__` field for `#[pyclass]`. +pub trait PyClassWeakRef { + const OFFSET: Option = None; + fn new() -> Self; + unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python) {} + private_decl! {} +} + +/// Zero-sized dummy field. +pub struct PyClassDummySlot; + +impl PyClassDict for PyClassDummySlot { + private_impl! {} + fn new() -> Self { + PyClassDummySlot + } +} + +impl PyClassWeakRef for PyClassDummySlot { + private_impl! {} + fn new() -> Self { + PyClassDummySlot + } +} + +/// Actual dict field, which holds the pointer to `__dict__`. +/// +/// `#[pyclass(dict)]` automatically adds this. +#[repr(transparent)] +pub struct PyClassDictSlot(*mut ffi::PyObject); + +impl PyClassDict for PyClassDictSlot { + private_impl! {} + const OFFSET: Option = Some(-POINTER_SIZE); + fn new() -> Self { + Self(std::ptr::null_mut()) + } + unsafe fn clear_dict(&mut self, _py: Python) { + if !self.0.is_null() { + ffi::PyDict_Clear(self.0) + } + } +} + +/// Actual weakref field, which holds the pointer to `__weakref__`. +/// +/// `#[pyclass(weakref)]` automatically adds this. +#[repr(transparent)] +pub struct PyClassWeakRefSlot(*mut ffi::PyObject); + +impl PyClassWeakRef for PyClassWeakRefSlot { + private_impl! {} + const OFFSET: Option = Some(-POINTER_SIZE); + fn new() -> Self { + Self(std::ptr::null_mut()) + } + unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python) { + if !self.0.is_null() { + ffi::PyObject_ClearWeakRefs(obj) + } + } +} diff --git a/src/python.rs b/src/python.rs index c667c05cd05..e5b31ae8f31 100644 --- a/src/python.rs +++ b/src/python.rs @@ -7,7 +7,7 @@ use crate::ffi; use crate::gil::{self, GILGuard}; use crate::instance::AsPyRef; use crate::object::PyObject; -use crate::type_object::{PyTypeInfo, PyTypeObject}; +use crate::type_object::{PyObjectLayout, PyTypeInfo, PyTypeObject}; use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::AsPyPointer; use crate::{FromPyPointer, IntoPyPointer, PyTryFrom}; @@ -272,25 +272,6 @@ impl<'p> Python<'p> { } impl<'p> Python<'p> { - pub(crate) unsafe fn unchecked_downcast(self, ob: &PyAny) -> &'p T { - if T::OFFSET == 0 { - &*(ob as *const _ as *const T) - } else { - let ptr = (ob.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - &*ptr - } - } - - #[allow(clippy::cast_ref_to_mut)] // FIXME - pub(crate) unsafe fn unchecked_mut_downcast(self, ob: &PyAny) -> &'p mut T { - if T::OFFSET == 0 { - &mut *(ob as *const _ as *mut T) - } else { - let ptr = (ob.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - &mut *ptr - } - } - /// Register object in release pool, and try to downcast to specific type. pub fn checked_cast_as(self, obj: PyObject) -> Result<&'p T, PyDowncastError> where @@ -306,7 +287,7 @@ impl<'p> Python<'p> { T: PyTypeInfo, { let p = gil::register_owned(self, obj.into_nonnull()); - self.unchecked_downcast(p) + T::ConcreteLayout::internal_ref_cast(p) } /// Register `ffi::PyObject` pointer in release pool diff --git a/src/type_object.rs b/src/type_object.rs index 1c1a493e94f..1aedc12d887 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -1,24 +1,66 @@ // Copyright (c) 2017-present PyO3 Project and Contributors - //! Python type object information -use crate::class::methods::PyMethodDefType; -use crate::err::{PyErr, PyResult}; -use crate::instance::{Py, PyNativeType}; -use crate::types::PyAny; -use crate::types::PyType; -use crate::AsPyPointer; -use crate::IntoPyPointer; -use crate::Python; -use crate::{class, ffi, gil}; -use class::methods::PyMethodsProtocol; -use std::collections::HashMap; -use std::ffi::CString; -use std::os::raw::c_void; -use std::ptr::{self, NonNull}; +use crate::instance::Py; +use crate::pyclass_init::PyObjectInit; +use crate::types::{PyAny, PyType}; +use crate::{ffi, AsPyPointer, Python}; +use std::ptr::NonNull; + +/// `T: PyObjectLayout` represents that `T` is a concrete representaion of `U` in Python heap. +/// E.g., `PyClassShell` is a concrete representaion of all `pyclass`es, and `ffi::PyObject` +/// is of `PyAny`. +/// +/// This trait is intended to be used internally. +pub trait PyObjectLayout { + const IS_NATIVE_TYPE: bool = true; + + fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { + None + } + + unsafe fn internal_ref_cast(obj: &PyAny) -> &T { + &*(obj as *const _ as *const T) + } + + #[allow(clippy::mut_from_ref)] + unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { + &mut *(obj as *const _ as *const T as *mut T) + } + + unsafe fn py_init(&mut self, _value: T) {} + unsafe fn py_drop(&mut self, _py: Python) {} +} + +/// `T: PyObjectSizedLayout` represents `T` is not a instance of +/// [`PyVarObject`](https://docs.python.org/3.8/c-api/structures.html?highlight=pyvarobject#c.PyVarObject). +/// , in addition that `T` is a concrete representaion of `U`. +/// +/// `pyclass`es need this trait for their base class. +pub trait PyObjectSizedLayout: PyObjectLayout + Sized {} + +/// Our custom type flags +#[doc(hidden)] +pub mod type_flags { + /// type object supports python GC + pub const GC: usize = 1; + + /// Type object supports python weak references + pub const WEAKREF: usize = 1 << 1; + + /// Type object can be used as the base type of another type + pub const BASETYPE: usize = 1 << 2; + + /// The instances of this type have a dictionary containing instance variables + pub const DICT: usize = 1 << 3; + + /// The class declared by #[pyclass(extends=~)] + pub const EXTENDED: usize = 1 << 4; +} /// Python type information. -pub trait PyTypeInfo { +/// All Python native types(e.g., `PyDict`) and `#[pyclass]` structs implement this trait. +pub trait PyTypeInfo: Sized { /// Type of objects to store in PyObject struct type Type; @@ -31,17 +73,17 @@ pub trait PyTypeInfo { /// Class doc string const DESCRIPTION: &'static str = "\0"; - /// Size of the rust PyObject structure (PyObject + rust structure) - const SIZE: usize; - - /// `Type` instance offset inside PyObject structure - const OFFSET: isize; - /// Type flags (ie PY_TYPE_FLAG_GC, PY_TYPE_FLAG_WEAKREF) const FLAGS: usize = 0; /// Base class - type BaseType: PyTypeInfo; + type BaseType: PyTypeInfo + PyTypeObject; + + /// Layout + type ConcreteLayout: PyObjectLayout; + + /// Initializer for layout + type Initializer: PyObjectInit; /// PyTypeObject instance for this type, which might still need to /// be initialized @@ -58,180 +100,6 @@ pub trait PyTypeInfo { } } -/// type object supports python GC -pub const PY_TYPE_FLAG_GC: usize = 1; - -/// Type object supports python weak references -pub const PY_TYPE_FLAG_WEAKREF: usize = 1 << 1; - -/// Type object can be used as the base type of another type -pub const PY_TYPE_FLAG_BASETYPE: usize = 1 << 2; - -/// The instances of this type have a dictionary containing instance variables -pub const PY_TYPE_FLAG_DICT: usize = 1 << 3; - -/// Special object that is used for python object creation. -/// `pyo3` library automatically creates this object for class `__new__` method. -/// Behavior is undefined if constructor of custom class does not initialze -/// instance of `PyRawObject` with rust value with `init` method. -/// Calling of `__new__` method of base class is developer's responsibility. -/// -/// Example of custom class implementation with `__new__` method: -/// ``` -/// use pyo3::prelude::*; -/// -/// #[pyclass] -/// struct MyClass { } -/// -/// #[pymethods] -/// impl MyClass { -/// #[new] -/// fn new(obj: &PyRawObject) { -/// obj.init(MyClass { }) -/// } -/// } -/// ``` -#[allow(dead_code)] -pub struct PyRawObject { - ptr: *mut ffi::PyObject, - /// Type object of class which __new__ method get called - tp_ptr: *mut ffi::PyTypeObject, - /// Type object of top most class in inheritance chain, - /// it might be python class. - curr_ptr: *mut ffi::PyTypeObject, - // initialized: usize, -} - -impl PyRawObject { - #[must_use] - pub unsafe fn new( - py: Python, - tp_ptr: *mut ffi::PyTypeObject, - curr_ptr: *mut ffi::PyTypeObject, - ) -> PyResult { - let alloc = (*curr_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); - let ptr = alloc(curr_ptr, 0); - - if !ptr.is_null() { - Ok(PyRawObject { - ptr, - tp_ptr, - curr_ptr, - // initialized: 0, - }) - } else { - PyErr::fetch(py).into() - } - } - - #[must_use] - pub unsafe fn new_with_ptr( - py: Python, - ptr: *mut ffi::PyObject, - tp_ptr: *mut ffi::PyTypeObject, - curr_ptr: *mut ffi::PyTypeObject, - ) -> PyResult { - if !ptr.is_null() { - Ok(PyRawObject { - ptr, - tp_ptr, - curr_ptr, - // initialized: 0, - }) - } else { - PyErr::fetch(py).into() - } - } - - pub fn init(&self, value: T) { - unsafe { - // The `as *mut u8` part is required because the offset is in bytes - let ptr = (self.ptr as *mut u8).offset(T::OFFSET) as *mut T; - std::ptr::write(ptr, value); - } - } - - /// Type object - pub fn type_object(&self) -> &PyType { - unsafe { PyType::from_type_ptr(self.py(), self.curr_ptr) } - } -} - -impl AsRef for PyRawObject { - #[inline] - fn as_ref(&self) -> &T { - // TODO: check is object initialized - unsafe { - let ptr = (self.ptr as *mut u8).offset(T::OFFSET) as *mut T; - ptr.as_ref().unwrap() - } - } -} - -impl IntoPyPointer for PyRawObject { - fn into_ptr(self) -> *mut ffi::PyObject { - // TODO: panic if not all types initialized - self.ptr - } -} - -unsafe impl PyNativeType for PyRawObject {} - -pub(crate) unsafe fn pytype_drop(py: Python, obj: *mut ffi::PyObject) { - if T::OFFSET != 0 { - let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T; - std::ptr::drop_in_place(ptr); - pytype_drop::(py, obj); - } -} - -/// A Python object allocator that is usable as a base type for `#[pyclass]` -/// -/// All native types and all `#[pyclass]` types use the default functions, while -/// [PyObjectWithFreeList](crate::freelist::PyObjectWithFreeList) gets a special version. -pub trait PyObjectAlloc: PyTypeInfo + Sized { - unsafe fn alloc(_py: Python) -> *mut ffi::PyObject { - let tp_ptr = Self::type_object(); - let alloc = (*tp_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); - alloc(tp_ptr, 0) - } - - /// Calls the rust destructor for the object and frees the memory - /// (usually by calling ptr->ob_type->tp_free). - /// This function is used as tp_dealloc implementation. - unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) { - Self::drop(py, obj); - - if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 { - return; - } - - match Self::type_object().tp_free { - Some(free) => free(obj as *mut c_void), - None => { - let ty = ffi::Py_TYPE(obj); - if ffi::PyType_IS_GC(ty) != 0 { - ffi::PyObject_GC_Del(obj as *mut c_void); - } else { - ffi::PyObject_Free(obj as *mut c_void); - } - - // For heap types, PyType_GenericAlloc calls INCREF on the type objects, - // so we need to call DECREF here: - if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { - ffi::Py_DECREF(ty as *mut ffi::PyObject); - } - } - } - } - - #[allow(unconditional_recursion)] - /// Calls the rust destructor for the object. - unsafe fn drop(py: Python, obj: *mut ffi::PyObject) { - pytype_drop::(py, obj); - } -} - /// Python object types that have a corresponding type object. /// /// This trait is marked unsafe because not fulfilling the contract for [PyTypeObject::init_type] @@ -246,272 +114,3 @@ pub unsafe trait PyTypeObject { unsafe { Py::from_borrowed_ptr(Self::init_type().as_ptr() as *mut ffi::PyObject) } } } - -unsafe impl PyTypeObject for T -where - T: PyTypeInfo + PyMethodsProtocol + PyObjectAlloc, -{ - fn init_type() -> NonNull { - let type_object = unsafe { ::type_object() }; - - if (type_object.tp_flags & ffi::Py_TPFLAGS_READY) == 0 { - // automatically initialize the class on-demand - let gil = Python::acquire_gil(); - let py = gil.python(); - - initialize_type::(py, ::MODULE).unwrap_or_else(|e| { - e.print(py); - panic!("An error occurred while initializing class {}", Self::NAME) - }); - } - - unsafe { NonNull::new_unchecked(type_object) } - } -} - -/// Python object types that can be instanciated with [Self::create()] -/// -/// We can't just make this a part of [PyTypeObject] because exceptions have -/// no PyTypeInfo -pub trait PyTypeCreate: PyObjectAlloc + PyTypeObject + Sized { - /// Create PyRawObject which can be initialized with rust value - #[must_use] - fn create(py: Python) -> PyResult { - Self::init_type(); - - unsafe { - let ptr = Self::alloc(py); - PyRawObject::new_with_ptr( - py, - ptr, - ::type_object(), - ::type_object(), - ) - } - } -} - -impl PyTypeCreate for T where T: PyObjectAlloc + PyTypeObject + Sized {} - -/// Register new type in python object system. -#[cfg(not(Py_LIMITED_API))] -pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> -where - T: PyObjectAlloc + PyTypeInfo + PyMethodsProtocol, -{ - let type_object: &mut ffi::PyTypeObject = unsafe { T::type_object() }; - let base_type_object: &mut ffi::PyTypeObject = - unsafe { ::type_object() }; - - // PyPy will segfault if passed only a nul terminator as `tp_doc`. - // ptr::null() is OK though. - if T::DESCRIPTION == "\0" { - type_object.tp_doc = ptr::null(); - } else { - type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _; - }; - - type_object.tp_base = base_type_object; - - let name = match module_name { - Some(module_name) => format!("{}.{}", module_name, T::NAME), - None => T::NAME.to_string(), - }; - let name = CString::new(name).expect("Module name/type name must not contain NUL byte"); - type_object.tp_name = name.into_raw(); - - // dealloc - type_object.tp_dealloc = Some(tp_dealloc_callback::); - - // type size - type_object.tp_basicsize = ::SIZE as ffi::Py_ssize_t; - - let mut offset = T::SIZE; - // weakref support (check py3cls::py_class::impl_class) - if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 { - offset -= std::mem::size_of::<*const ffi::PyObject>(); - type_object.tp_weaklistoffset = offset as isize; - } - - // __dict__ support - let has_dict = T::FLAGS & PY_TYPE_FLAG_DICT != 0; - if has_dict { - offset -= std::mem::size_of::<*const ffi::PyObject>(); - type_object.tp_dictoffset = offset as isize; - } - - // GC support - ::update_type_object(type_object); - - // descriptor protocol - ::tp_as_descr(type_object); - - // iterator methods - ::tp_as_iter(type_object); - - // basic methods - ::tp_as_object(type_object); - - fn to_ptr(value: Option) -> *mut T { - value - .map(|v| Box::into_raw(Box::new(v))) - .unwrap_or_else(ptr::null_mut) - } - - // number methods - type_object.tp_as_number = to_ptr(::tp_as_number()); - // mapping methods - type_object.tp_as_mapping = - to_ptr(::tp_as_mapping()); - // sequence methods - type_object.tp_as_sequence = - to_ptr(::tp_as_sequence()); - // async methods - type_object.tp_as_async = to_ptr(::tp_as_async()); - // buffer protocol - type_object.tp_as_buffer = to_ptr(::tp_as_buffer()); - - // normal methods - let (new, call, mut methods) = py_class_method_defs::(); - if !methods.is_empty() { - methods.push(ffi::PyMethodDef_INIT); - type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as *mut _; - } - - // __new__ method - type_object.tp_new = new; - // __call__ method - type_object.tp_call = call; - - // properties - let mut props = py_class_properties::(); - - if has_dict { - props.push(ffi::PyGetSetDef_DICT); - } - if !props.is_empty() { - props.push(ffi::PyGetSetDef_INIT); - type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as *mut _; - } - - // set type flags - py_class_flags::(type_object); - - // register type object - unsafe { - if ffi::PyType_Ready(type_object) == 0 { - Ok(type_object as *mut ffi::PyTypeObject) - } else { - PyErr::fetch(py).into() - } - } -} - -unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) -where - T: PyObjectAlloc, -{ - let py = Python::assume_gil_acquired(); - let _pool = gil::GILPool::new_no_pointers(py); - ::dealloc(py, obj) -} -fn py_class_flags(type_object: &mut ffi::PyTypeObject) { - if type_object.tp_traverse != None - || type_object.tp_clear != None - || T::FLAGS & PY_TYPE_FLAG_GC != 0 - { - type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; - } else { - type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; - } - if T::FLAGS & PY_TYPE_FLAG_BASETYPE != 0 { - type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE; - } -} - -fn py_class_method_defs() -> ( - Option, - Option, - Vec, -) { - let mut defs = Vec::new(); - let mut call = None; - let mut new = None; - - for def in T::py_methods() { - match *def { - PyMethodDefType::New(ref def) => { - if let class::methods::PyMethodType::PyNewFunc(meth) = def.ml_meth { - new = Some(meth) - } - } - PyMethodDefType::Call(ref def) => { - if let class::methods::PyMethodType::PyCFunctionWithKeywords(meth) = def.ml_meth { - call = Some(meth) - } else { - panic!("Method type is not supoorted by tp_call slot") - } - } - PyMethodDefType::Method(ref def) - | PyMethodDefType::Class(ref def) - | PyMethodDefType::Static(ref def) => { - defs.push(def.as_method_def()); - } - _ => (), - } - } - - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - - py_class_async_methods::(&mut defs); - - (new, call, defs) -} - -fn py_class_async_methods(defs: &mut Vec) { - for def in ::methods() { - defs.push(def.as_method_def()); - } -} - -fn py_class_properties() -> Vec { - let mut defs = HashMap::new(); - - for def in T::py_methods() { - match *def { - PyMethodDefType::Getter(ref getter) => { - let name = getter.name.to_string(); - if !defs.contains_key(&name) { - let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); - } - let def = defs.get_mut(&name).expect("Failed to call get_mut"); - getter.copy_to(def); - } - PyMethodDefType::Setter(ref setter) => { - let name = setter.name.to_string(); - if !defs.contains_key(&name) { - let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); - } - let def = defs.get_mut(&name).expect("Failed to call get_mut"); - setter.copy_to(def); - } - _ => (), - } - } - - defs.values().cloned().collect() -} diff --git a/src/types/any.rs b/src/types/any.rs index dbbc5cf0647..f17adc3f04d 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,7 +1,7 @@ -use crate::conversion::AsPyPointer; +use crate::conversion::PyTryFrom; use crate::err::PyDowncastError; use crate::internal_tricks::Unsendable; -use crate::{ffi, PyObject, PyRef, PyRefMut, PyTryFrom, PyTypeInfo}; +use crate::{ffi, PyObject}; /// Represents a python's [Any](https://docs.python.org/3/library/typing.html#typing.Any) type. /// We can convert all python objects as `PyAny`. @@ -23,8 +23,16 @@ use crate::{ffi, PyObject, PyRef, PyRefMut, PyTryFrom, PyTypeInfo}; /// ``` #[repr(transparent)] pub struct PyAny(PyObject, Unsendable); +impl crate::type_object::PyObjectLayout for ffi::PyObject {} +impl crate::type_object::PyObjectSizedLayout for ffi::PyObject {} pyobject_native_type_named!(PyAny); -pyobject_native_type_convert!(PyAny, ffi::PyBaseObject_Type, ffi::PyObject_Check); +pyobject_native_type_convert!( + PyAny, + ffi::PyObject, + ffi::PyBaseObject_Type, + Some("builtins"), + ffi::PyObject_Check +); impl PyAny { pub fn downcast_ref(&self) -> Result<&T, PyDowncastError> @@ -41,21 +49,3 @@ impl PyAny { T::try_from_mut(self) } } - -impl<'a, T> From> for &'a PyAny -where - T: PyTypeInfo, -{ - fn from(pref: PyRef<'a, T>) -> &'a PyAny { - unsafe { &*(pref.as_ptr() as *const PyAny) } - } -} - -impl<'a, T> From> for &'a PyAny -where - T: PyTypeInfo, -{ - fn from(pref: PyRefMut<'a, T>) -> &'a PyAny { - unsafe { &*(pref.as_ptr() as *const PyAny) } - } -} diff --git a/src/types/boolobject.rs b/src/types/boolobject.rs index 7204090e61c..3f88a2a2e19 100644 --- a/src/types/boolobject.rs +++ b/src/types/boolobject.rs @@ -13,7 +13,7 @@ use crate::{PyTryFrom, ToPyObject}; #[repr(transparent)] pub struct PyBool(PyObject, Unsendable); -pyobject_native_type!(PyBool, ffi::PyBool_Type, ffi::PyBool_Check); +pyobject_native_type!(PyBool, ffi::PyObject, ffi::PyBool_Type, ffi::PyBool_Check); impl PyBool { /// Depending on `val`, returns `py.True()` or `py.False()`. diff --git a/src/types/bytearray.rs b/src/types/bytearray.rs index bdc7455102a..8f08a2ecbb1 100644 --- a/src/types/bytearray.rs +++ b/src/types/bytearray.rs @@ -13,7 +13,7 @@ use std::slice; #[repr(transparent)] pub struct PyByteArray(PyObject, Unsendable); -pyobject_native_type!(PyByteArray, ffi::PyByteArray_Type, ffi::PyByteArray_Check); +pyobject_native_var_type!(PyByteArray, ffi::PyByteArray_Type, ffi::PyByteArray_Check); impl PyByteArray { /// Creates a new Python bytearray object. diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 2734d6912ee..8b26b82ed26 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -18,12 +18,7 @@ use std::str; #[repr(transparent)] pub struct PyBytes(PyObject, Unsendable); -pyobject_native_type!( - PyBytes, - ffi::PyBytes_Type, - Some("builtins"), - ffi::PyBytes_Check -); +pyobject_native_var_type!(PyBytes, ffi::PyBytes_Type, ffi::PyBytes_Check); impl PyBytes { /// Creates a new Python byte string object. diff --git a/src/types/complex.rs b/src/types/complex.rs index 4eef6792a13..40e213ca2a2 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -13,7 +13,12 @@ use std::os::raw::c_double; #[repr(transparent)] pub struct PyComplex(PyObject, Unsendable); -pyobject_native_type!(PyComplex, ffi::PyComplex_Type, ffi::PyComplex_Check); +pyobject_native_type!( + PyComplex, + ffi::PyComplexObject, + ffi::PyComplex_Type, + ffi::PyComplex_Check +); impl PyComplex { /// Creates a new Python `PyComplex` object, from its real and imaginary values. diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 15f898950d7..293eabff09b 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -69,6 +69,7 @@ pub trait PyTimeAccess { pub struct PyDate(PyObject, Unsendable); pyobject_native_type!( PyDate, + crate::ffi::PyDateTime_Date, *PyDateTimeAPI.DateType, Some("datetime"), PyDate_Check @@ -124,6 +125,7 @@ impl PyDateAccess for PyDate { pub struct PyDateTime(PyObject, Unsendable); pyobject_native_type!( PyDateTime, + crate::ffi::PyDateTime_DateTime, *PyDateTimeAPI.DateTimeType, Some("datetime"), PyDateTime_Check @@ -233,6 +235,7 @@ impl PyTimeAccess for PyDateTime { pub struct PyTime(PyObject, Unsendable); pyobject_native_type!( PyTime, + crate::ffi::PyDateTime_Time, *PyDateTimeAPI.TimeType, Some("datetime"), PyTime_Check @@ -317,6 +320,7 @@ impl PyTimeAccess for PyTime { pub struct PyTzInfo(PyObject, Unsendable); pyobject_native_type!( PyTzInfo, + crate::ffi::PyObject, *PyDateTimeAPI.TZInfoType, Some("datetime"), PyTZInfo_Check @@ -326,6 +330,7 @@ pyobject_native_type!( pub struct PyDelta(PyObject, Unsendable); pyobject_native_type!( PyDelta, + crate::ffi::PyDateTime_Delta, *PyDateTimeAPI.DeltaType, Some("datetime"), PyDelta_Check diff --git a/src/types/dict.rs b/src/types/dict.rs index 74bbd0c412f..9d2cc8f477b 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -19,7 +19,12 @@ use std::{cmp, collections, hash}; #[repr(transparent)] pub struct PyDict(PyObject, Unsendable); -pyobject_native_type!(PyDict, ffi::PyDict_Type, ffi::PyDict_Check); +pyobject_native_type!( + PyDict, + ffi::PyDictObject, + ffi::PyDict_Type, + ffi::PyDict_Check +); impl PyDict { /// Creates a new empty dictionary. diff --git a/src/types/floatob.rs b/src/types/floatob.rs index a27bfb5b73c..4f7d464b794 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -25,7 +25,12 @@ use std::os::raw::c_double; #[repr(transparent)] pub struct PyFloat(PyObject, Unsendable); -pyobject_native_type!(PyFloat, ffi::PyFloat_Type, ffi::PyFloat_Check); +pyobject_native_type!( + PyFloat, + ffi::PyFloatObject, + ffi::PyFloat_Type, + ffi::PyFloat_Check +); impl PyFloat { /// Creates a new Python `float` object. diff --git a/src/types/list.rs b/src/types/list.rs index 484ba5d0ae4..b354ffbcde4 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -17,7 +17,7 @@ use crate::{ToBorrowedObject, ToPyObject}; #[repr(transparent)] pub struct PyList(PyObject, Unsendable); -pyobject_native_type!(PyList, ffi::PyList_Type, ffi::PyList_Check); +pyobject_native_var_type!(PyList, ffi::PyList_Type, ffi::PyList_Check); impl PyList { /// Construct a new list with the given elements. diff --git a/src/types/mod.rs b/src/types/mod.rs index a73bb1888d7..53a7f1e1ee8 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -79,10 +79,12 @@ macro_rules! pyobject_native_type_named ( ); #[macro_export] -macro_rules! pyobject_native_type ( - ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { +macro_rules! pyobject_native_type { + ($name: ty, $layout: path, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { + impl $crate::type_object::PyObjectLayout<$name> for $layout {} + impl $crate::type_object::PyObjectSizedLayout<$name> for $layout {} pyobject_native_type_named!($name $(,$type_param)*); - pyobject_native_type_convert!($name, $typeobject, $module, $checkfunction $(,$type_param)*); + pyobject_native_type_convert!($name, $layout, $typeobject, $module, $checkfunction $(,$type_param)*); impl<'a, $($type_param,)*> ::std::convert::From<&'a $name> for &'a $crate::types::PyAny { fn from(ob: &'a $name) -> Self { @@ -90,22 +92,45 @@ macro_rules! pyobject_native_type ( } } }; + ($name: ty, $layout: path, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { + pyobject_native_type! { + $name, $layout, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* + } + }; +} + +#[macro_export] +macro_rules! pyobject_native_var_type { + ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { + impl $crate::type_object::PyObjectLayout<$name> for $crate::ffi::PyObject {} + pyobject_native_type_named!($name $(,$type_param)*); + pyobject_native_type_convert!($name, $crate::ffi::PyObject, + $typeobject, $module, $checkfunction $(,$type_param)*); + impl<'a, $($type_param,)*> ::std::convert::From<&'a $name> for &'a $crate::types::PyAny { + fn from(ob: &'a $name) -> Self { + unsafe{&*(ob as *const $name as *const $crate::types::PyAny)} + } + } + }; ($name: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { - pyobject_native_type!{$name, $typeobject, Some("builtins"), $checkfunction $(,$type_param)*} + pyobject_native_var_type! { + $name, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* + } }; -); +} #[macro_export] macro_rules! pyobject_native_type_convert( - ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { + ($name: ty, $layout: path, $typeobject: expr, + $module: expr, $checkfunction: path $(,$type_param: ident)*) => { impl<$($type_param,)*> $crate::type_object::PyTypeInfo for $name { type Type = (); type BaseType = $crate::types::PyAny; + type ConcreteLayout = $layout; + type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; const NAME: &'static str = stringify!($name); const MODULE: Option<&'static str> = $module; - const SIZE: usize = ::std::mem::size_of::<$crate::ffi::PyObject>(); - const OFFSET: isize = 0; #[inline] unsafe fn type_object() -> &'static mut $crate::ffi::PyTypeObject { @@ -120,12 +145,12 @@ macro_rules! pyobject_native_type_convert( } } - impl<$($type_param,)*> $crate::type_object::PyObjectAlloc for $name {} - unsafe impl<$($type_param,)*> $crate::type_object::PyTypeObject for $name { fn init_type() -> std::ptr::NonNull<$crate::ffi::PyTypeObject> { unsafe { - std::ptr::NonNull::new_unchecked(::type_object() as *mut _) + std::ptr::NonNull::new_unchecked( + ::type_object() as *mut _ + ) } } } @@ -159,9 +184,6 @@ macro_rules! pyobject_native_type_convert( } } }; - ($name: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { - pyobject_native_type_convert!{$name, $typeobject, Some("builtins"), $checkfunction $(,$type_param)*} - }; ); mod any; diff --git a/src/types/module.rs b/src/types/module.rs index fa5babe3747..d152c5291ab 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -9,15 +9,11 @@ use crate::instance::PyNativeType; use crate::internal_tricks::Unsendable; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; -use crate::type_object::PyTypeCreate; +use crate::pyclass::PyClass; use crate::type_object::PyTypeObject; use crate::types::PyTuple; use crate::types::{PyAny, PyDict, PyList}; -use crate::AsPyPointer; -use crate::IntoPy; -use crate::Py; -use crate::Python; -use crate::ToPyObject; +use crate::{AsPyPointer, IntoPy, Py, Python, ToPyObject}; use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::str; @@ -26,7 +22,7 @@ use std::str; #[repr(transparent)] pub struct PyModule(PyObject, Unsendable); -pyobject_native_type!(PyModule, ffi::PyModule_Type, ffi::PyModule_Check); +pyobject_native_var_type!(PyModule, ffi::PyModule_Type, ffi::PyModule_Check); impl PyModule { /// Create a new module object with the `__name__` attribute set to name. @@ -173,7 +169,7 @@ impl PyModule { /// and adds the type to this module. pub fn add_class(&self) -> PyResult<()> where - T: PyTypeCreate, + T: PyClass, { self.add(T::NAME, ::type_object()) } diff --git a/src/types/num.rs b/src/types/num.rs index ba191f79bbd..b964af11c9b 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -120,12 +120,7 @@ macro_rules! int_convert_128 { #[repr(transparent)] pub struct PyLong(PyObject, Unsendable); -pyobject_native_type!( - PyLong, - ffi::PyLong_Type, - Some("builtins"), - ffi::PyLong_Check -); +pyobject_native_var_type!(PyLong, ffi::PyLong_Type, ffi::PyLong_Check); macro_rules! int_fits_c_long { ($rust_type:ty) => { diff --git a/src/types/set.rs b/src/types/set.rs index f565af6ffa4..87f01a75728 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -21,8 +21,13 @@ pub struct PySet(PyObject, Unsendable); #[repr(transparent)] pub struct PyFrozenSet(PyObject, Unsendable); -pyobject_native_type!(PySet, ffi::PySet_Type, Some("builtins"), ffi::PySet_Check); -pyobject_native_type!(PyFrozenSet, ffi::PyFrozenSet_Type, ffi::PyFrozenSet_Check); +pyobject_native_type!(PySet, ffi::PySetObject, ffi::PySet_Type, ffi::PySet_Check); +pyobject_native_type!( + PyFrozenSet, + ffi::PySetObject, + ffi::PyFrozenSet_Type, + ffi::PyFrozenSet_Check +); impl PySet { /// Creates a new set. diff --git a/src/types/slice.rs b/src/types/slice.rs index d618a2a903b..fbea20edda3 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -15,7 +15,12 @@ use std::os::raw::c_long; #[repr(transparent)] pub struct PySlice(PyObject, Unsendable); -pyobject_native_type!(PySlice, ffi::PySlice_Type, ffi::PySlice_Check); +pyobject_native_type!( + PySlice, + ffi::PySliceObject, + ffi::PySlice_Type, + ffi::PySlice_Check +); /// Represents a Python `slice` indices pub struct PySliceIndices { diff --git a/src/types/string.rs b/src/types/string.rs index ad216a7e810..51d490f15a5 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -24,7 +24,7 @@ use std::str; #[repr(transparent)] pub struct PyString(PyObject, Unsendable); -pyobject_native_type!(PyString, ffi::PyUnicode_Type, ffi::PyUnicode_Check); +pyobject_native_var_type!(PyString, ffi::PyUnicode_Type, ffi::PyUnicode_Check); impl PyString { /// Creates a new Python string object. @@ -207,7 +207,7 @@ mod test { let s = "Hello Python"; let py_string = s.to_object(py); - let s2: &str = FromPyObject::extract(py_string.as_ref(py).into()).unwrap(); + let s2: &str = FromPyObject::extract(py_string.as_ref(py)).unwrap(); assert_eq!(s, s2); } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index e2c7eb450f4..f7a6264006c 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -18,7 +18,7 @@ use std::slice; #[repr(transparent)] pub struct PyTuple(PyObject, Unsendable); -pyobject_native_type!(PyTuple, ffi::PyTuple_Type, ffi::PyTuple_Check); +pyobject_native_var_type!(PyTuple, ffi::PyTuple_Type, ffi::PyTuple_Check); impl PyTuple { /// Construct a new tuple with the given elements. @@ -119,7 +119,7 @@ impl<'a> Iterator for PyTupleIterator<'a> { if self.index < self.slice.len() { let item = self.slice[self.index].as_ref(self.py); self.index += 1; - Some(item.into()) + Some(item) } else { None } diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index f079595cde9..ae18a3b1d48 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -17,7 +17,7 @@ use std::ffi::CStr; #[repr(transparent)] pub struct PyType(PyObject, Unsendable); -pyobject_native_type!(PyType, ffi::PyType_Type, ffi::PyType_Check); +pyobject_native_var_type!(PyType, ffi::PyType_Type, ffi::PyType_Check); impl PyType { #[inline] diff --git a/tests/test_arithmetics.rs b/tests/test_arithmetics.rs old mode 100755 new mode 100644 diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index c7b56940858..4c340acfb73 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; use pyo3::py_run; -use pyo3::type_object::initialize_type; +use pyo3::pyclass::initialize_type; mod common; diff --git a/tests/test_class_new.rs b/tests/test_class_new.rs index bc1a1a1ed72..ce3107e9f23 100644 --- a/tests/test_class_new.rs +++ b/tests/test_class_new.rs @@ -1,5 +1,4 @@ use pyo3::prelude::*; -use pyo3::PyRawObject; #[pyclass] struct EmptyClassWithNew {} @@ -7,8 +6,8 @@ struct EmptyClassWithNew {} #[pymethods] impl EmptyClassWithNew { #[new] - fn new(obj: &PyRawObject) { - obj.init(EmptyClassWithNew {}); + fn new() -> EmptyClassWithNew { + EmptyClassWithNew {} } } @@ -25,6 +24,7 @@ fn empty_class_with_new() { } #[pyclass] +#[derive(Debug)] struct NewWithOneArg { _data: i32, } @@ -32,8 +32,8 @@ struct NewWithOneArg { #[pymethods] impl NewWithOneArg { #[new] - fn new(obj: &PyRawObject, arg: i32) { - obj.init(NewWithOneArg { _data: arg }) + fn new(arg: i32) -> NewWithOneArg { + NewWithOneArg { _data: arg } } } @@ -56,11 +56,11 @@ struct NewWithTwoArgs { #[pymethods] impl NewWithTwoArgs { #[new] - fn new(obj: &PyRawObject, arg1: i32, arg2: i32) { - obj.init(NewWithTwoArgs { + fn new(arg1: i32, arg2: i32) -> Self { + NewWithTwoArgs { _data1: arg1, _data2: arg2, - }) + } } } diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs old mode 100755 new mode 100644 diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 4dd10062dc8..6fecc4c77cf 100755 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -4,11 +4,9 @@ use pyo3::class::{ PyContextProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol, PySequenceProtocol, }; use pyo3::exceptions::{IndexError, ValueError}; -use pyo3::ffi; use pyo3::prelude::*; -use pyo3::py_run; use pyo3::types::{IntoPyDict, PyAny, PyBytes, PySlice, PyType}; -use pyo3::AsPyPointer; +use pyo3::{ffi, py_run, AsPyPointer, PyClassShell}; use std::convert::TryFrom; use std::{isize, iter}; @@ -55,11 +53,11 @@ struct Iterator { #[pyproto] impl<'p> PyIterProtocol for Iterator { - fn __iter__(slf: PyRefMut) -> PyResult> { + fn __iter__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.into()) } - fn __next__(mut slf: PyRefMut) -> PyResult> { + fn __next__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.iter.next()) } } @@ -252,7 +250,7 @@ fn setitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyRef::new(py, SetItem { key: 0, val: 0 }).unwrap(); + let c = PyClassShell::new_ref(py, SetItem { key: 0, val: 0 }).unwrap(); py_run!(py, c, "c[1] = 2"); assert_eq!(c.key, 1); assert_eq!(c.val, 2); @@ -277,7 +275,7 @@ fn delitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyRef::new(py, DelItem { key: 0 }).unwrap(); + let c = PyClassShell::new_ref(py, DelItem { key: 0 }).unwrap(); py_run!(py, c, "del c[1]"); assert_eq!(c.key, 1); py_expect_exception!(py, c, "c[1] = 2", NotImplementedError); @@ -306,7 +304,7 @@ fn setdelitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyRef::new(py, SetDelItem { val: None }).unwrap(); + let c = PyClassShell::new_ref(py, SetDelItem { val: None }).unwrap(); py_run!(py, c, "c[1] = 2"); assert_eq!(c.val, Some(2)); py_run!(py, c, "del c[1]"); @@ -385,7 +383,7 @@ fn context_manager() { let gil = Python::acquire_gil(); let py = gil.python(); - let mut c = PyRefMut::new(py, ContextManager { exit_called: false }).unwrap(); + let c = PyClassShell::new_mut(py, ContextManager { exit_called: false }).unwrap(); py_run!(py, c, "with c as x: assert x == 42"); assert!(c.exit_called); @@ -457,7 +455,7 @@ struct DunderDictSupport {} fn dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, DunderDictSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, DunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -472,7 +470,7 @@ fn dunder_dict_support() { fn access_dunder_dict() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, DunderDictSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, DunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -490,7 +488,7 @@ struct WeakRefDunderDictSupport {} fn weakref_dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, WeakRefDunderDictSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, WeakRefDunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -515,7 +513,7 @@ impl PyObjectProtocol for ClassWithGetAttr { fn getattr_doesnt_override_member() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, ClassWithGetAttr { data: 4 }).unwrap(); + let inst = PyClassShell::new_ref(py, ClassWithGetAttr { data: 4 }).unwrap(); py_assert!(py, inst, "inst.data == 4"); py_assert!(py, inst, "inst.a == 8"); } diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 26465438a25..8f2d0923513 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -1,13 +1,9 @@ use pyo3::class::PyGCProtocol; use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; -use pyo3::ffi; use pyo3::prelude::*; -use pyo3::py_run; -use pyo3::types::PyAny; -use pyo3::types::PyTuple; -use pyo3::AsPyPointer; -use pyo3::PyRawObject; +use pyo3::types::{PyAny, PyTuple}; +use pyo3::{ffi, py_run, AsPyPointer, PyClassShell}; use std::cell::RefCell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -157,7 +153,7 @@ fn gc_integration() { { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new( + let inst = PyClassShell::new_mut( py, GCIntegration { self_ref: RefCell::new(py.None()), @@ -192,7 +188,7 @@ impl PyGCProtocol for GCIntegration2 { fn gc_integration2() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, GCIntegration2 {}).unwrap(); + let inst = PyClassShell::new_ref(py, GCIntegration2 {}).unwrap(); py_run!(py, inst, "import gc; assert inst in gc.get_objects()"); } @@ -203,7 +199,7 @@ struct WeakRefSupport {} fn weakref_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, WeakRefSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, WeakRefSupport {}).unwrap(); py_run!( py, inst, @@ -219,8 +215,8 @@ struct BaseClassWithDrop { #[pymethods] impl BaseClassWithDrop { #[new] - fn new(obj: &PyRawObject) { - obj.init(BaseClassWithDrop { data: None }) + fn new() -> BaseClassWithDrop { + BaseClassWithDrop { data: None } } } @@ -240,9 +236,11 @@ struct SubClassWithDrop { #[pymethods] impl SubClassWithDrop { #[new] - fn new(obj: &PyRawObject) { - obj.init(SubClassWithDrop { data: None }); - BaseClassWithDrop::new(obj); + fn new() -> (Self, BaseClassWithDrop) { + ( + SubClassWithDrop { data: None }, + BaseClassWithDrop { data: None }, + ) } } diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index cacf5652c7d..7610e81fa5b 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -1,7 +1,6 @@ use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::{IntoPyDict, PyList}; -use std::isize; mod common; diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 69f70c644cd..4bbd3701213 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -1,9 +1,10 @@ use pyo3::prelude::*; use pyo3::py_run; + #[cfg(feature = "unsound-subclass")] use pyo3::types::IntoPyDict; -use std::isize; +use pyo3::types::{PyDict, PySet}; mod common; #[pyclass] @@ -35,8 +36,8 @@ fn subclass() { #[pymethods] impl BaseClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(BaseClass { val1: 10 }) + fn new() -> Self { + BaseClass { val1: 10 } } } @@ -49,9 +50,8 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(SubClass { val2: 5 }); - BaseClass::new(obj); + fn new() -> (Self, BaseClass) { + (SubClass { val2: 5 }, BaseClass { val1: 10 }) } } @@ -59,8 +59,106 @@ impl SubClass { fn inheritance_with_new_methods() { let gil = Python::acquire_gil(); let py = gil.python(); - let _typebase = py.get_type::(); let typeobj = py.get_type::(); let inst = typeobj.call((), None).unwrap(); py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5"); } + +#[pyclass] +struct BaseClassWithResult { + _val: usize, +} + +#[pymethods] +impl BaseClassWithResult { + #[new] + fn new(value: isize) -> PyResult { + Ok(Self { + _val: std::convert::TryFrom::try_from(value)?, + }) + } +} + +#[pyclass(extends=BaseClassWithResult)] +struct SubClass2 {} + +#[pymethods] +impl SubClass2 { + #[new] + fn new(value: isize) -> PyResult<(Self, BaseClassWithResult)> { + let base = BaseClassWithResult::new(value)?; + Ok((Self {}, base)) + } +} + +#[test] +fn handle_result_in_new() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let subclass = py.get_type::(); + py_run!( + py, + subclass, + r#" +try: + subclass(-10) + assert Fals +except ValueError as e: + pass +except Exception as e: + raise e +"# + ); +} + +#[pyclass(extends=PySet)] +struct SetWithName { + #[pyo3(get(name))] + _name: &'static str, +} + +#[pymethods] +impl SetWithName { + #[new] + fn new() -> Self { + SetWithName { _name: "Hello :)" } + } +} + +#[test] +fn inherit_set() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set_sub = pyo3::pyclass::PyClassShell::new_ref(py, SetWithName::new()).unwrap(); + py_run!( + py, + set_sub, + r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""# + ); +} + +#[pyclass(extends=PyDict)] +struct DictWithName { + #[pyo3(get(name))] + _name: &'static str, +} + +#[pymethods] +impl DictWithName { + #[new] + fn new() -> Self { + DictWithName { _name: "Hello :)" } + } +} + +#[test] +fn inherit_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let dict_sub = pyo3::pyclass::PyClassShell::new_ref(py, DictWithName::new()).unwrap(); + py_run!( + py, + dict_sub, + r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub._name == "Hello :)""# + ); +} diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs index 6160da0fc66..17a0203bf57 100644 --- a/tests/test_mapping.rs +++ b/tests/test_mapping.rs @@ -15,20 +15,19 @@ struct Mapping { #[pymethods] impl Mapping { #[new] - fn new(obj: &PyRawObject, elements: Option<&PyList>) -> PyResult<()> { + fn new(elements: Option<&PyList>) -> PyResult { if let Some(pylist) = elements { let mut elems = HashMap::with_capacity(pylist.len()); for (i, pyelem) in pylist.into_iter().enumerate() { let elem = String::extract(pyelem)?; elems.insert(elem, i); } - obj.init(Self { index: elems }); + Ok(Self { index: elems }) } else { - obj.init(Self { + Ok(Self { index: HashMap::new(), - }); + }) } - Ok(()) } } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index b2295f008dd..153bc2b5fca 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -1,7 +1,7 @@ use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType}; -use pyo3::PyRawObject; +use pyo3::PyClassShell; mod common; @@ -23,7 +23,7 @@ fn instance_method() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyRefMut::new(py, InstanceMethod { member: 42 }).unwrap(); + let obj = PyClassShell::new_mut(py, InstanceMethod { member: 42 }).unwrap(); assert_eq!(obj.method().unwrap(), 42); let d = [("obj", obj)].into_py_dict(py); py.run("assert obj.method() == 42", None, Some(d)).unwrap(); @@ -49,7 +49,7 @@ fn instance_method_with_args() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyRefMut::new(py, InstanceMethodWithArgs { member: 7 }).unwrap(); + let obj = PyClassShell::new_mut(py, InstanceMethodWithArgs { member: 7 }).unwrap(); assert_eq!(obj.method(6).unwrap(), 42); let d = [("obj", obj)].into_py_dict(py); py.run("assert obj.method(3) == 21", None, Some(d)).unwrap(); @@ -63,8 +63,8 @@ struct ClassMethod {} #[pymethods] impl ClassMethod { #[new] - fn new(obj: &PyRawObject) { - obj.init(ClassMethod {}) + fn new() -> Self { + ClassMethod {} } #[classmethod] @@ -137,8 +137,8 @@ struct StaticMethod {} #[pymethods] impl StaticMethod { #[new] - fn new(obj: &PyRawObject) { - obj.init(StaticMethod {}) + fn new() -> Self { + StaticMethod {} } #[staticmethod] @@ -395,7 +395,7 @@ impl MethodWithLifeTime { fn method_with_lifetime() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyRef::new(py, MethodWithLifeTime {}).unwrap(); + let obj = PyClassShell::new_ref(py, MethodWithLifeTime {}).unwrap(); py_run!( py, obj, diff --git a/tests/test_pyself.rs b/tests/test_pyself.rs index 5f2ce854178..00eebfb128f 100644 --- a/tests/test_pyself.rs +++ b/tests/test_pyself.rs @@ -2,7 +2,7 @@ use pyo3; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyString}; -use pyo3::PyIterProtocol; +use pyo3::{AsPyRef, PyClassShell, PyIterProtocol}; use std::collections::HashMap; mod common; @@ -10,20 +10,23 @@ mod common; /// Assumes it's a file reader or so. /// Inspired by https://github.com/jothan/cordoba, thanks. #[pyclass] -#[derive(Clone)] +#[derive(Clone, Debug)] struct Reader { inner: HashMap, } #[pymethods] impl Reader { - fn clone_ref(slf: PyRef) -> PyRef { + fn clone_ref(slf: &PyClassShell) -> &PyClassShell { slf } - fn clone_ref_with_py<'py>(slf: PyRef<'py, Self>, _py: Python<'py>) -> PyRef<'py, Self> { + fn clone_ref_with_py<'py>( + slf: &'py PyClassShell, + _py: Python<'py>, + ) -> &'py PyClassShell { slf } - fn get_iter(slf: PyRef, keys: Py) -> PyResult { + fn get_iter(slf: &PyClassShell, keys: Py) -> PyResult { Ok(Iter { reader: slf.into(), keys, @@ -31,7 +34,7 @@ impl Reader { }) } fn get_iter_and_reset( - mut slf: PyRefMut, + slf: &mut PyClassShell, keys: Py, py: Python, ) -> PyResult { @@ -54,13 +57,15 @@ struct Iter { #[pyproto] impl PyIterProtocol for Iter { - fn __iter__(slf: PyRefMut) -> PyResult { + fn __iter__(slf: &mut PyClassShell) -> PyResult { let py = unsafe { Python::assume_gil_acquired() }; Ok(slf.to_object(py)) } - fn __next__(mut slf: PyRefMut) -> PyResult> { + + fn __next__(slf: &mut PyClassShell) -> PyResult> { let py = unsafe { Python::assume_gil_acquired() }; - match slf.keys.as_ref(py).as_bytes().get(slf.idx) { + let bytes = slf.keys.as_ref(py).as_bytes(); + match bytes.get(slf.idx) { Some(&b) => { let res = slf .reader @@ -84,7 +89,7 @@ fn reader() -> Reader { } #[test] -fn test_nested_iter() { +fn test_nested_iter1() { let gil = Python::acquire_gil(); let py = gil.python(); let reader: PyObject = reader().into_py(py); @@ -108,7 +113,7 @@ fn test_clone_ref() { fn test_nested_iter_reset() { let gil = Python::acquire_gil(); let py = gil.python(); - let reader = PyRef::new(py, reader()).unwrap(); + let reader = PyClassShell::new_ref(py, reader()).unwrap(); py_assert!( py, reader, diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index 6241f33ecf3..9b97aa6b69b 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -14,20 +14,19 @@ struct ByteSequence { #[pymethods] impl ByteSequence { #[new] - fn new(obj: &PyRawObject, elements: Option<&PyList>) -> PyResult<()> { + fn new(elements: Option<&PyList>) -> PyResult { if let Some(pylist) = elements { let mut elems = Vec::with_capacity(pylist.len()); for pyelem in pylist.into_iter() { let elem = u8::extract(pyelem)?; elems.push(elem); } - obj.init(Self { elements: elems }); + Ok(Self { elements: elems }) } else { - obj.init(Self { + Ok(Self { elements: Vec::new(), - }); + }) } - Ok(()) } } diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index 72df1fdf111..6406cbf590b 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -1,5 +1,5 @@ use pyo3::prelude::*; -use pyo3::{types::PyType, wrap_pyfunction, wrap_pymodule}; +use pyo3::{types::PyType, wrap_pyfunction, wrap_pymodule, PyClassShell}; mod common; @@ -44,9 +44,9 @@ fn class_with_docs_and_signature() { impl MyClass { #[new] #[args(a, b = "None", "*", c = 42)] - fn __new__(obj: &PyRawObject, a: i32, b: Option, c: i32) { + fn __new__(a: i32, b: Option, c: i32) -> Self { let _ = (a, b, c); - obj.init(Self {}); + Self {} } } @@ -76,9 +76,9 @@ fn class_with_signature() { impl MyClass { #[new] #[args(a, b = "None", "*", c = 42)] - fn __new__(obj: &PyRawObject, a: i32, b: Option, c: i32) { + fn __new__(a: i32, b: Option, c: i32) -> Self { let _ = (a, b, c); - obj.init(Self {}); + Self {} } } @@ -144,7 +144,7 @@ fn test_methods() { let _ = a; } #[text_signature = "($self, b)"] - fn pyself_method(_this: PyRef, b: i32) { + fn pyself_method(_this: &PyClassShell, b: i32) { let _ = b; } #[classmethod] diff --git a/tests/test_various.rs b/tests/test_various.rs index 563166bd7cd..c4b8e6c878a 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -1,9 +1,8 @@ use pyo3::prelude::*; -use pyo3::type_object::initialize_type; +use pyo3::pyclass::initialize_type; use pyo3::types::IntoPyDict; use pyo3::types::{PyDict, PyTuple}; -use pyo3::{py_run, wrap_pyfunction}; -use std::isize; +use pyo3::{py_run, wrap_pyfunction, AsPyRef, PyClassShell}; mod common; @@ -83,8 +82,8 @@ fn intopytuple_pyclass() { let py = gil.python(); let tup = ( - PyRef::new(py, SimplePyClass {}).unwrap(), - PyRef::new(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), ); py_assert!(py, tup, "type(tup[0]).__name__ == 'SimplePyClass'"); py_assert!(py, tup, "type(tup[0]).__name__ == type(tup[1]).__name__"); @@ -108,8 +107,8 @@ fn pytuple_pyclass_iter() { let tup = PyTuple::new( py, [ - PyRef::new(py, SimplePyClass {}).unwrap(), - PyRef::new(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), ] .iter(), ); @@ -124,12 +123,12 @@ struct PickleSupport {} #[pymethods] impl PickleSupport { #[new] - fn new(obj: &PyRawObject) { - obj.init({ PickleSupport {} }); + fn new() -> PickleSupport { + PickleSupport {} } pub fn __reduce__<'py>( - slf: PyRef, + slf: &'py PyClassShell, py: Python<'py>, ) -> PyResult<(PyObject, &'py PyTuple, PyObject)> { let cls = slf.to_object(py).getattr(py, "__class__")?; @@ -155,7 +154,7 @@ fn test_pickle() { module.add_class::().unwrap(); add_module(py, module).unwrap(); initialize_type::(py, Some("test_module")).unwrap(); - let inst = PyRef::new(py, PickleSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, PickleSupport {}).unwrap(); py_run!( py, inst,