diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c4caaa4876..91e77ba7673 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,6 +94,10 @@ jobs: - if: matrix.python-version != 'pypy3' name: Test run: cargo test --features "num-bigint num-complex" --target ${{ matrix.platform.rust-target }} + # Run tests again, but in abi3 mode + - if: matrix.python-version != 'pypy3' + name: Test (abi3) + run: cargo test --no-default-features --features "abi3,macros" --target ${{ matrix.platform.rust-target }} - name: Test proc-macro code run: cargo test --manifest-path=pyo3-derive-backend/Cargo.toml --target ${{ matrix.platform.rust-target }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c193ee93299..4b58c9c1c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Drop support for Python 3.5 (as it is now end-of-life). [#1250](https://github.com/PyO3/pyo3/pull/1250) ### Added +- Add support for building for CPython limited API. This required a few minor changes to runtime behaviour of of pyo3 `#[pyclass]` types. See the migration guide for full details. [#1152](https://github.com/PyO3/pyo3/pull/1152) - Add argument names to `TypeError` messages generated by pymethod wrappers. [#1212](https://github.com/PyO3/pyo3/pull/1212) - Add `PyEval_SetProfile` and `PyEval_SetTrace` to FFI. [#1255](https://github.com/PyO3/pyo3/pull/1255) - Add context.h functions (`PyContext_New`, etc) to FFI. [#1259](https://github.com/PyO3/pyo3/pull/1259) ### Changed +- Change return type `PyType::name()` from `Cow` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152) +- `#[pyclass(subclass)]` is now required for subclassing from Rust (was previously just required for subclassing from Python). [#1152](https://github.com/PyO3/pyo3/pull/1152) - Change `PyIterator` to be consistent with other native types: it is now used as `&PyIterator` instead of `PyIterator<'a>`. [#1176](https://github.com/PyO3/pyo3/pull/1176) - Change formatting of `PyDowncastError` messages to be closer to Python's builtin error messages. [#1212](https://github.com/PyO3/pyo3/pull/1212) diff --git a/Cargo.toml b/Cargo.toml index 776d9e87049..72a907135a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,10 @@ rustversion = "1.0" [features] default = ["macros"] macros = ["ctor", "indoc", "inventory", "paste", "pyo3cls", "unindent"] +# Use the Python limited API. See https://www.python.org/dev/peps/pep-0384/ for +# more. +abi3 = [] + # Optimizes PyObject to Vec conversion and so on. nightly = [] @@ -43,11 +47,6 @@ nightly = [] # so that the module can also be used with statically linked python interpreters. extension-module = [] -# The stable cpython abi as defined in PEP 384. Currently broken with -# many compilation errors. Pull Requests working towards fixing that -# are welcome. -# abi3 = [] - [workspace] members = [ "pyo3cls", diff --git a/build.rs b/build.rs index e02b12fcb6f..1d4d78f61ab 100644 --- a/build.rs +++ b/build.rs @@ -557,6 +557,11 @@ fn run_python_script(interpreter: &Path, script: &str) -> Result { fn get_library_link_name(version: &PythonVersion, ld_version: &str) -> String { if cfg!(target_os = "windows") { + // Mirrors the behavior in CPython's `PC/pyconfig.h`. + if env::var_os("CARGO_FEATURE_ABI3").is_some() { + return "python3".to_string(); + } + let minor_or_empty_string = match version.minor { Some(minor) => format!("{}", minor), None => String::new(), diff --git a/examples/rustapi_module/tests/test_datetime.py b/examples/rustapi_module/tests/test_datetime.py index 421dec707ce..cfce7ce08b1 100644 --- a/examples/rustapi_module/tests/test_datetime.py +++ b/examples/rustapi_module/tests/test_datetime.py @@ -1,6 +1,7 @@ import datetime as pdt import platform import struct +import re import sys import pytest @@ -310,4 +311,5 @@ def test_tz_class_introspection(): tzi = rdt.TzClass() assert tzi.__class__ == rdt.TzClass - assert repr(tzi).startswith(" for some reason. + assert re.match(r"^<[\w\.]*TzClass object at", repr(tzi)) diff --git a/guide/src/building_and_distribution.md b/guide/src/building_and_distribution.md index f0b76f0d9ca..b0ed67c13cc 100644 --- a/guide/src/building_and_distribution.md +++ b/guide/src/building_and_distribution.md @@ -36,7 +36,27 @@ On Linux/macOS you might have to change `LD_LIBRARY_PATH` to include libpython, ## Distribution -There are two ways to distribute your module as a Python package: The old, [setuptools-rust](https://github.com/PyO3/setuptools-rust), and the new, [maturin](https://github.com/pyo3/maturin). setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). maturin doesn't need any configuration files, however it does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)) and requires a rigid project structure, while setuptools-rust allows (and sometimes requires) configuration with python code. +There are two ways to distribute your module as a Python package: The old, [setuptools-rust], and the new, [maturin]. setuptools-rust needs several configuration files (`setup.py`, `MANIFEST.in`, `build-wheels.sh`, etc.). maturin doesn't need any configuration files, however it does not support some functionality of setuptools such as package data ([pyo3/maturin#258](https://github.com/PyO3/maturin/issues/258)) and requires a rigid project structure, while setuptools-rust allows (and sometimes requires) configuration with python code. + +## `Py_LIMITED_API`/`abi3` + +By default, Python extension modules can only be used with the same Python version they were compiled against -- if you build an extension module with Python 3.5, you can't import it using Python 3.8. [PEP 384](https://www.python.org/dev/peps/pep-0384/) introduced the idea of the limited Python API, which would have a stable ABI enabling extension modules built with it to be used against multiple Python versions. This is also known as `abi3`. + +Note that [maturin] >= 0.9.0 or [setuptools-rust] >= 0.12.0 is going to support `abi3` wheels. +See the [corresponding](https://github.com/PyO3/maturin/pull/353) [PRs](https://github.com/PyO3/setuptools-rust/pull/82) for more. + +There are three steps involved in making use of `abi3` when building Python packages as wheels: + +1. Enable the `abi3` feature in `pyo3`. This ensures `pyo3` only calls Python C-API functions which are part of the stable API, and on Windows also ensures that the project links against the correct shared object (no special behavior is required on other platforms): + +```toml +[dependencies] +pyo3 = { version = "...", features = ["abi3"]} +``` + +2. Ensure that the built shared objects are correctly marked as `abi3`. This is accomplished by telling your build system that you're using the limited API. + +3. Ensure that the `.whl` is correctly marked as `abi3`. For projects using `setuptools`, this is accomplished by passing `--py-limited-api=cp3x` (where `x` is the minimum Python version supported by the wheel, e.g. `--py-limited-api=cp35` for Python 3.5) to `setup.py bdist_wheel`. ## Cross Compiling @@ -83,3 +103,8 @@ cargo build --target x86_64-pc-windows-gnu ## Bazel For an example of how to build python extensions using Bazel, see https://github.com/TheButlah/rules_pyo3 + + +[maturin]: https://github.com/PyO3/maturin +[setuptools-rust]: https://github.com/PyO3/setuptools-rust + diff --git a/guide/src/class.md b/guide/src/class.md index e284efc4de3..bd3ad360b78 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -205,7 +205,7 @@ or by `self_.into_super()` as `PyRef`. ```rust # use pyo3::prelude::*; -#[pyclass] +#[pyclass(subclass)] struct BaseClass { val1: usize, } @@ -222,7 +222,7 @@ impl BaseClass { } } -#[pyclass(extends=BaseClass)] +#[pyclass(extends=BaseClass, subclass)] struct SubClass { val2: usize, } @@ -266,12 +266,14 @@ impl SubSubClass { ``` You can also inherit native types such as `PyDict`, if they implement -[`PySizedLayout`](https://docs.rs/pyo3/latest/pyo3/type_object/trait.PySizedLayout.html). +[`PySizedLayout`](https://docs.rs/pyo3/latest/pyo3/type_object/trait.PySizedLayout.html). However, this is not supported when building for the Python limited API (aka the `abi3` feature of PyO3). However, because of some technical problems, we don't currently provide safe upcasting methods for types that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion. ```rust +# #[cfg(Py_LIMITED_API)] fn main() {} +# #[cfg(not(Py_LIMITED_API))] fn main() { # use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::{AsPyPointer, PyNativeType}; @@ -300,6 +302,7 @@ impl DictWithCounter { # let py = gil.python(); # let cnt = pyo3::PyCell::new(py, DictWithCounter::new()).unwrap(); # pyo3::py_run!(py, cnt, "cnt.set('abc', 10); assert cnt['abc'] == 10") +# } ``` If `SubClass` does not provide a baseclass initialization, the compilation fails. @@ -769,13 +772,23 @@ impl pyo3::class::methods::HasMethodsInventory for MyClass { } pyo3::inventory::collect!(Pyo3MethodsInventoryForMyClass); -impl pyo3::class::proto_methods::HasProtoRegistry for MyClass { - fn registry() -> &'static pyo3::class::proto_methods::PyProtoRegistry { - static REGISTRY: pyo3::class::proto_methods::PyProtoRegistry - = pyo3::class::proto_methods::PyProtoRegistry::new(); - ®ISTRY + +pub struct Pyo3ProtoInventoryForMyClass { + def: pyo3::class::proto_methods::PyProtoMethodDef, +} +impl pyo3::class::proto_methods::PyProtoInventory for Pyo3ProtoInventoryForMyClass { + fn new(def: pyo3::class::proto_methods::PyProtoMethodDef) -> Self { + Self { def } + } + fn get(&'static self) -> &'static pyo3::class::proto_methods::PyProtoMethodDef { + &self.def } } +impl pyo3::class::proto_methods::HasProtoInventory for MyClass { + type ProtoMethods = Pyo3ProtoInventoryForMyClass; +} +pyo3::inventory::collect!(Pyo3ProtoInventoryForMyClass); + impl pyo3::pyclass::PyClassSend for MyClass { type ThreadChecker = pyo3::pyclass::ThreadCheckerStub; diff --git a/guide/src/migration.md b/guide/src/migration.md index c1eb7a7b1ff..9caab85e22d 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -3,6 +3,18 @@ This guide can help you upgrade code through breaking changes from one PyO3 version to the next. For a detailed list of all changes, see the [CHANGELOG](changelog.md). +## from 0.12.* to 0.13 + +### Runtime changes to support the CPython limited API + +In PyO3 `0.13` support was added for compiling against the CPython limited API. This had a number of implications for _all_ PyO3 users, described here. + +The largest of these is that all types created from PyO3 are what CPython calls "heap" types. The specific implications of this are: + +- If you wish to subclass one of these types _from Rust_ you must mark it `#[pyclass(subclass)]`, as you would if you wished to allow subclassing it from Python code. +- Type objects are now mutable - Python code can set attributes on them. +- `__module__` on types without `#[pyclass(module="mymodule")]` no longer returns `builtins`, it now raises `AttributeError`. + ## from 0.11.* to 0.12 ### `PyErr` has been reworked diff --git a/guide/src/trait_bounds.md b/guide/src/trait_bounds.md index f845b5c898c..06942b3eaeb 100644 --- a/guide/src/trait_bounds.md +++ b/guide/src/trait_bounds.md @@ -408,8 +408,8 @@ impl Model for UserModel { .call_method("get_results", (), None) .unwrap(); - if py_result.get_type().name() != "list" { - panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); + if py_result.get_type().name().unwrap() != "list" { + panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap()); } py_result.extract() }) @@ -536,8 +536,8 @@ impl Model for UserModel { .call_method("get_results", (), None) .unwrap(); - if py_result.get_type().name() != "list" { - panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name()); + if py_result.get_type().name().unwrap() != "list" { + panic!("Expected a list for the get_results() method signature, got {}", py_result.get_type().name().unwrap()); } py_result.extract() }) diff --git a/pyo3-derive-backend/src/defs.rs b/pyo3-derive-backend/src/defs.rs index 0bdfedf9915..eda87b5f013 100644 --- a/pyo3-derive-backend/src/defs.rs +++ b/pyo3-derive-backend/src/defs.rs @@ -6,16 +6,14 @@ use std::collections::HashSet; pub struct Proto { /// The name of this protocol. E.g., Iter. pub name: &'static str, - /// The name of slot table. E.g., PyIterMethods. - pub slot_table: &'static str, - /// The name of the setter used to set the table to `PyProtoRegistry`. - pub set_slot_table: &'static str, + /// Extension trait that has `get_*` methods + pub extension_trait: &'static str, /// All methods. pub methods: &'static [MethodProto], /// All methods registered as normal methods like `#[pymethods]`. pub py_methods: &'static [PyMethod], /// All methods registered to the slot table. - slot_setters: &'static [SlotSetter], + slot_getters: &'static [SlotGetter], } impl Proto { @@ -32,13 +30,13 @@ impl Proto { self.py_methods.iter().find(|m| query == m.name) } // Since the order matters, we expose only the iterator instead of the slice. - pub(crate) fn setters( + pub(crate) fn slot_getters( &self, mut implemented_protocols: HashSet, ) -> impl Iterator { - self.slot_setters.iter().filter_map(move |setter| { + self.slot_getters.iter().filter_map(move |getter| { // If any required method is not implemented, we skip this setter. - if setter + if getter .proto_names .iter() .any(|name| !implemented_protocols.contains(*name)) @@ -47,10 +45,10 @@ impl Proto { } // To use 'paired' setter in priority, we remove used protocols. // For example, if set_add_radd is already used, we shouldn't use set_add and set_radd. - for name in setter.proto_names { + for name in getter.proto_names { implemented_protocols.remove(*name); } - Some(setter.set_function) + Some(getter.get_function) }) } } @@ -82,27 +80,26 @@ impl PyMethod { } /// Represents a setter used to register a method to the method table. -struct SlotSetter { +struct SlotGetter { /// Protocols necessary for invoking this setter. /// E.g., we need `__setattr__` and `__delattr__` for invoking `set_setdelitem`. pub proto_names: &'static [&'static str], /// The name of the setter called to the method table. - pub set_function: &'static str, + pub get_function: &'static str, } -impl SlotSetter { - const fn new(names: &'static [&'static str], set_function: &'static str) -> Self { - SlotSetter { +impl SlotGetter { + const fn new(names: &'static [&'static str], get_function: &'static str) -> Self { + SlotGetter { proto_names: names, - set_function, + get_function, } } } pub const OBJECT: Proto = Proto { name: "Object", - slot_table: "pyo3::class::basic::PyObjectMethods", - set_slot_table: "set_basic_methods", + extension_trait: "pyo3::class::basic::PyBasicSlots", methods: &[ MethodProto::Binary { name: "__getattr__", @@ -156,23 +153,22 @@ pub const OBJECT: Proto = Proto { PyMethod::new("__bytes__", "pyo3::class::basic::BytesProtocolImpl"), PyMethod::new("__unicode__", "pyo3::class::basic::UnicodeProtocolImpl"), ], - slot_setters: &[ - SlotSetter::new(&["__str__"], "set_str"), - SlotSetter::new(&["__repr__"], "set_repr"), - SlotSetter::new(&["__hash__"], "set_hash"), - SlotSetter::new(&["__getattr__"], "set_getattr"), - SlotSetter::new(&["__richcmp__"], "set_richcompare"), - SlotSetter::new(&["__setattr__", "__delattr__"], "set_setdelattr"), - SlotSetter::new(&["__setattr__"], "set_setattr"), - SlotSetter::new(&["__delattr__"], "set_delattr"), - SlotSetter::new(&["__bool__"], "set_bool"), + slot_getters: &[ + SlotGetter::new(&["__str__"], "get_str"), + SlotGetter::new(&["__repr__"], "get_repr"), + SlotGetter::new(&["__hash__"], "get_hash"), + SlotGetter::new(&["__getattr__"], "get_getattr"), + SlotGetter::new(&["__richcmp__"], "get_richcmp"), + SlotGetter::new(&["__setattr__", "__delattr__"], "get_setdelattr"), + SlotGetter::new(&["__setattr__"], "get_setattr"), + SlotGetter::new(&["__delattr__"], "get_delattr"), + SlotGetter::new(&["__bool__"], "get_bool"), ], }; pub const ASYNC: Proto = Proto { name: "Async", - slot_table: "pyo3::ffi::PyAsyncMethods", - set_slot_table: "set_async_methods", + extension_trait: "pyo3::class::pyasync::PyAsyncSlots", methods: &[ MethodProto::UnaryS { name: "__await__", @@ -211,17 +207,16 @@ pub const ASYNC: Proto = Proto { "pyo3::class::pyasync::PyAsyncAexitProtocolImpl", ), ], - slot_setters: &[ - SlotSetter::new(&["__await__"], "set_await"), - SlotSetter::new(&["__aiter__"], "set_aiter"), - SlotSetter::new(&["__anext__"], "set_anext"), + slot_getters: &[ + SlotGetter::new(&["__await__"], "get_await"), + SlotGetter::new(&["__aiter__"], "get_aiter"), + SlotGetter::new(&["__anext__"], "get_anext"), ], }; pub const BUFFER: Proto = Proto { name: "Buffer", - slot_table: "pyo3::ffi::PyBufferProcs", - set_slot_table: "set_buffer_methods", + extension_trait: "pyo3::class::buffer::PyBufferSlots", methods: &[ MethodProto::Unary { name: "bf_getbuffer", @@ -233,16 +228,15 @@ pub const BUFFER: Proto = Proto { }, ], py_methods: &[], - slot_setters: &[ - SlotSetter::new(&["bf_getbuffer"], "set_getbuffer"), - SlotSetter::new(&["bf_releasebuffer"], "set_releasebuffer"), + slot_getters: &[ + SlotGetter::new(&["bf_getbuffer"], "get_getbuffer"), + SlotGetter::new(&["bf_releasebuffer"], "get_releasebuffer"), ], }; pub const CONTEXT: Proto = Proto { name: "Context", - slot_table: "", - set_slot_table: "", + extension_trait: "", methods: &[ MethodProto::Unary { name: "__enter__", @@ -266,13 +260,12 @@ pub const CONTEXT: Proto = Proto { "pyo3::class::context::PyContextExitProtocolImpl", ), ], - slot_setters: &[], + slot_getters: &[], }; pub const GC: Proto = Proto { name: "GC", - slot_table: "pyo3::class::gc::PyGCMethods", - set_slot_table: "set_gc_methods", + extension_trait: "pyo3::class::gc::PyGCSlots", methods: &[ MethodProto::Free { name: "__traverse__", @@ -284,16 +277,15 @@ pub const GC: Proto = Proto { }, ], py_methods: &[], - slot_setters: &[ - SlotSetter::new(&["__traverse__"], "set_traverse"), - SlotSetter::new(&["__clear__"], "set_clear"), + slot_getters: &[ + SlotGetter::new(&["__traverse__"], "get_traverse"), + SlotGetter::new(&["__clear__"], "get_clear"), ], }; pub const DESCR: Proto = Proto { name: "Descriptor", - slot_table: "pyo3::class::descr::PyDescrMethods", - set_slot_table: "set_descr_methods", + extension_trait: "pyo3::class::descr::PyDescrSlots", methods: &[ MethodProto::TernaryS { name: "__get__", @@ -327,16 +319,15 @@ pub const DESCR: Proto = Proto { "pyo3::class::context::PyDescrNameProtocolImpl", ), ], - slot_setters: &[ - SlotSetter::new(&["__get__"], "set_descr_get"), - SlotSetter::new(&["__set__"], "set_descr_set"), + slot_getters: &[ + SlotGetter::new(&["__get__"], "get_descr_get"), + SlotGetter::new(&["__set__"], "get_descr_set"), ], }; pub const ITER: Proto = Proto { name: "Iter", - slot_table: "pyo3::class::iter::PyIterMethods", - set_slot_table: "set_iter_methods", + extension_trait: "pyo3::class::iter::PyIterSlots", py_methods: &[], methods: &[ MethodProto::UnaryS { @@ -350,16 +341,15 @@ pub const ITER: Proto = Proto { proto: "pyo3::class::iter::PyIterNextProtocol", }, ], - slot_setters: &[ - SlotSetter::new(&["__iter__"], "set_iter"), - SlotSetter::new(&["__next__"], "set_iternext"), + slot_getters: &[ + SlotGetter::new(&["__iter__"], "get_iter"), + SlotGetter::new(&["__next__"], "get_iternext"), ], }; pub const MAPPING: Proto = Proto { name: "Mapping", - slot_table: "pyo3::ffi::PyMappingMethods", - set_slot_table: "set_mapping_methods", + extension_trait: "pyo3::class::mapping::PyMappingSlots", methods: &[ MethodProto::Unary { name: "__len__", @@ -390,19 +380,18 @@ pub const MAPPING: Proto = Proto { "__reversed__", "pyo3::class::mapping::PyMappingReversedProtocolImpl", )], - slot_setters: &[ - SlotSetter::new(&["__len__"], "set_length"), - SlotSetter::new(&["__getitem__"], "set_getitem"), - SlotSetter::new(&["__setitem__", "__delitem__"], "set_setdelitem"), - SlotSetter::new(&["__setitem__"], "set_setitem"), - SlotSetter::new(&["__delitem__"], "set_delitem"), + slot_getters: &[ + SlotGetter::new(&["__len__"], "get_len"), + SlotGetter::new(&["__getitem__"], "get_getitem"), + SlotGetter::new(&["__setitem__", "__delitem__"], "get_setdelitem"), + SlotGetter::new(&["__setitem__"], "get_setitem"), + SlotGetter::new(&["__delitem__"], "get_delitem"), ], }; pub const SEQ: Proto = Proto { name: "Sequence", - slot_table: "pyo3::ffi::PySequenceMethods", - set_slot_table: "set_sequence_methods", + extension_trait: "pyo3::class::sequence::PySequenceSlots", methods: &[ MethodProto::Unary { name: "__len__", @@ -451,24 +440,23 @@ pub const SEQ: Proto = Proto { }, ], py_methods: &[], - slot_setters: &[ - SlotSetter::new(&["__len__"], "set_len"), - SlotSetter::new(&["__concat__"], "set_concat"), - SlotSetter::new(&["__repeat__"], "set_repeat"), - SlotSetter::new(&["__getitem__"], "set_getitem"), - SlotSetter::new(&["__setitem__", "__delitem__"], "set_setdelitem"), - SlotSetter::new(&["__setitem__"], "set_setitem"), - SlotSetter::new(&["__delitem__"], "set_delitem"), - SlotSetter::new(&["__contains__"], "set_contains"), - SlotSetter::new(&["__inplace_concat__"], "set_inplace_concat"), - SlotSetter::new(&["__inplace_repeat__"], "set_inplace_repeat"), + slot_getters: &[ + SlotGetter::new(&["__len__"], "get_len"), + SlotGetter::new(&["__concat__"], "get_concat"), + SlotGetter::new(&["__repeat__"], "get_repeat"), + SlotGetter::new(&["__getitem__"], "get_getitem"), + SlotGetter::new(&["__setitem__", "__delitem__"], "get_setdelitem"), + SlotGetter::new(&["__setitem__"], "get_setitem"), + SlotGetter::new(&["__delitem__"], "get_delitem"), + SlotGetter::new(&["__contains__"], "get_contains"), + SlotGetter::new(&["__inplace_concat__"], "get_inplace_concat"), + SlotGetter::new(&["__inplace_repeat__"], "get_inplace_repeat"), ], }; pub const NUM: Proto = Proto { name: "Number", - slot_table: "pyo3::ffi::PyNumberMethods", - set_slot_table: "set_number_methods", + extension_trait: "pyo3::class::number::PyNumberSlots", methods: &[ MethodProto::BinaryS { name: "__add__", @@ -771,66 +759,66 @@ pub const NUM: Proto = Proto { "pyo3::class::number::PyNumberRoundProtocolImpl", ), ], - slot_setters: &[ - SlotSetter::new(&["__add__", "__radd__"], "set_add_radd"), - SlotSetter::new(&["__add__"], "set_add"), - SlotSetter::new(&["__radd__"], "set_radd"), - SlotSetter::new(&["__sub__", "__rsub__"], "set_sub_rsub"), - SlotSetter::new(&["__sub__"], "set_sub"), - SlotSetter::new(&["__rsub__"], "set_rsub"), - SlotSetter::new(&["__mul__", "__rmul__"], "set_mul_rmul"), - SlotSetter::new(&["__mul__"], "set_mul"), - SlotSetter::new(&["__rmul__"], "set_rmul"), - SlotSetter::new(&["__mod__"], "set_mod"), - SlotSetter::new(&["__divmod__", "__rdivmod__"], "set_divmod_rdivmod"), - SlotSetter::new(&["__divmod__"], "set_divmod"), - SlotSetter::new(&["__rdivmod__"], "set_rdivmod"), - SlotSetter::new(&["__pow__", "__rpow__"], "set_pow_rpow"), - SlotSetter::new(&["__pow__"], "set_pow"), - SlotSetter::new(&["__rpow__"], "set_rpow"), - SlotSetter::new(&["__neg__"], "set_neg"), - SlotSetter::new(&["__pos__"], "set_pos"), - SlotSetter::new(&["__abs__"], "set_abs"), - SlotSetter::new(&["__invert__"], "set_invert"), - SlotSetter::new(&["__lshift__", "__rlshift__"], "set_lshift_rlshift"), - SlotSetter::new(&["__lshift__"], "set_lshift"), - SlotSetter::new(&["__rlshift__"], "set_rlshift"), - SlotSetter::new(&["__rshift__", "__rrshift__"], "set_rshift_rrshift"), - SlotSetter::new(&["__rshift__"], "set_rshift"), - SlotSetter::new(&["__rrshift__"], "set_rrshift"), - SlotSetter::new(&["__and__", "__rand__"], "set_and_rand"), - SlotSetter::new(&["__and__"], "set_and"), - SlotSetter::new(&["__rand__"], "set_rand"), - SlotSetter::new(&["__xor__", "__rxor__"], "set_xor_rxor"), - SlotSetter::new(&["__xor__"], "set_xor"), - SlotSetter::new(&["__rxor__"], "set_rxor"), - SlotSetter::new(&["__or__", "__ror__"], "set_or_ror"), - SlotSetter::new(&["__or__"], "set_or"), - SlotSetter::new(&["__ror__"], "set_ror"), - SlotSetter::new(&["__int__"], "set_int"), - SlotSetter::new(&["__float__"], "set_float"), - SlotSetter::new(&["__iadd__"], "set_iadd"), - SlotSetter::new(&["__isub__"], "set_isub"), - SlotSetter::new(&["__imul__"], "set_imul"), - SlotSetter::new(&["__imod__"], "set_imod"), - SlotSetter::new(&["__ipow__"], "set_ipow"), - SlotSetter::new(&["__ilshift__"], "set_ilshift"), - SlotSetter::new(&["__irshift__"], "set_irshift"), - SlotSetter::new(&["__iand__"], "set_iand"), - SlotSetter::new(&["__ixor__"], "set_ixor"), - SlotSetter::new(&["__ior__"], "set_ior"), - SlotSetter::new(&["__floordiv__", "__rfloordiv__"], "set_floordiv_rfloordiv"), - SlotSetter::new(&["__floordiv__"], "set_floordiv"), - SlotSetter::new(&["__rfloordiv__"], "set_rfloordiv"), - SlotSetter::new(&["__truediv__", "__rtruediv__"], "set_truediv_rtruediv"), - SlotSetter::new(&["__truediv__"], "set_truediv"), - SlotSetter::new(&["__rtruediv__"], "set_rtruediv"), - SlotSetter::new(&["__ifloordiv__"], "set_ifloordiv"), - SlotSetter::new(&["__itruediv__"], "set_itruediv"), - SlotSetter::new(&["__index__"], "set_index"), - SlotSetter::new(&["__matmul__", "__rmatmul__"], "set_matmul_rmatmul"), - SlotSetter::new(&["__matmul__"], "set_matmul"), - SlotSetter::new(&["__rmatmul__"], "set_rmatmul"), - SlotSetter::new(&["__imatmul__"], "set_imatmul"), + slot_getters: &[ + SlotGetter::new(&["__add__", "__radd__"], "get_add_radd"), + SlotGetter::new(&["__add__"], "get_add"), + SlotGetter::new(&["__radd__"], "get_radd"), + SlotGetter::new(&["__sub__", "__rsub__"], "get_sub_rsub"), + SlotGetter::new(&["__sub__"], "get_sub"), + SlotGetter::new(&["__rsub__"], "get_rsub"), + SlotGetter::new(&["__mul__", "__rmul__"], "get_mul_rmul"), + SlotGetter::new(&["__mul__"], "get_mul"), + SlotGetter::new(&["__rmul__"], "get_rmul"), + SlotGetter::new(&["__mod__"], "get_mod"), + SlotGetter::new(&["__divmod__", "__rdivmod__"], "get_divmod_rdivmod"), + SlotGetter::new(&["__divmod__"], "get_divmod"), + SlotGetter::new(&["__rdivmod__"], "get_rdivmod"), + SlotGetter::new(&["__pow__", "__rpow__"], "get_pow_rpow"), + SlotGetter::new(&["__pow__"], "get_pow"), + SlotGetter::new(&["__rpow__"], "get_rpow"), + SlotGetter::new(&["__neg__"], "get_neg"), + SlotGetter::new(&["__pos__"], "get_pos"), + SlotGetter::new(&["__abs__"], "get_abs"), + SlotGetter::new(&["__invert__"], "get_invert"), + SlotGetter::new(&["__lshift__", "__rlshift__"], "get_lshift_rlshift"), + SlotGetter::new(&["__lshift__"], "get_lshift"), + SlotGetter::new(&["__rlshift__"], "get_rlshift"), + SlotGetter::new(&["__rshift__", "__rrshift__"], "get_rshift_rrshift"), + SlotGetter::new(&["__rshift__"], "get_rshift"), + SlotGetter::new(&["__rrshift__"], "get_rrshift"), + SlotGetter::new(&["__and__", "__rand__"], "get_and_rand"), + SlotGetter::new(&["__and__"], "get_and"), + SlotGetter::new(&["__rand__"], "get_rand"), + SlotGetter::new(&["__xor__", "__rxor__"], "get_xor_rxor"), + SlotGetter::new(&["__xor__"], "get_xor"), + SlotGetter::new(&["__rxor__"], "get_rxor"), + SlotGetter::new(&["__or__", "__ror__"], "get_or_ror"), + SlotGetter::new(&["__or__"], "get_or"), + SlotGetter::new(&["__ror__"], "get_ror"), + SlotGetter::new(&["__int__"], "get_int"), + SlotGetter::new(&["__float__"], "get_float"), + SlotGetter::new(&["__iadd__"], "get_iadd"), + SlotGetter::new(&["__isub__"], "get_isub"), + SlotGetter::new(&["__imul__"], "get_imul"), + SlotGetter::new(&["__imod__"], "get_imod"), + SlotGetter::new(&["__ipow__"], "get_ipow"), + SlotGetter::new(&["__ilshift__"], "get_ilshift"), + SlotGetter::new(&["__irshift__"], "get_irshift"), + SlotGetter::new(&["__iand__"], "get_iand"), + SlotGetter::new(&["__ixor__"], "get_ixor"), + SlotGetter::new(&["__ior__"], "get_ior"), + SlotGetter::new(&["__floordiv__", "__rfloordiv__"], "get_floordiv_rfloordiv"), + SlotGetter::new(&["__floordiv__"], "get_floordiv"), + SlotGetter::new(&["__rfloordiv__"], "get_rfloordiv"), + SlotGetter::new(&["__truediv__", "__rtruediv__"], "get_truediv_rtruediv"), + SlotGetter::new(&["__truediv__"], "get_truediv"), + SlotGetter::new(&["__rtruediv__"], "get_rtruediv"), + SlotGetter::new(&["__ifloordiv__"], "get_ifloordiv"), + SlotGetter::new(&["__itruediv__"], "get_itruediv"), + SlotGetter::new(&["__index__"], "get_index"), + SlotGetter::new(&["__matmul__", "__rmatmul__"], "get_matmul_rmatmul"), + SlotGetter::new(&["__matmul__"], "get_matmul"), + SlotGetter::new(&["__rmatmul__"], "get_rmatmul"), + SlotGetter::new(&["__imatmul__"], "get_imatmul"), ], }; diff --git a/pyo3-derive-backend/src/from_pyobject.rs b/pyo3-derive-backend/src/from_pyobject.rs index 5ea0754d205..2275dfac886 100644 --- a/pyo3-derive-backend/src/from_pyobject.rs +++ b/pyo3-derive-backend/src/from_pyobject.rs @@ -72,7 +72,7 @@ impl<'a> Enum<'a> { }; quote!( #(#var_extracts)* - let type_name = obj.get_type().name(); + let type_name = obj.get_type().name()?; let err_msg = format!("'{}' object cannot be converted to '{}'", type_name, #error_names); Err(::pyo3::exceptions::PyTypeError::new_err(err_msg)) ) @@ -246,7 +246,7 @@ impl<'a> Container<'a> { let self_ty = &self.path; let mut fields: Punctuated = Punctuated::new(); for i in 0..len { - fields.push(quote!(slice[#i].extract()?)); + fields.push(quote!(s.get_item(#i).extract()?)); } let msg = if self.is_enum_variant { quote!(format!( @@ -262,7 +262,6 @@ impl<'a> Container<'a> { if s.len() != #len { return Err(::pyo3::exceptions::PyValueError::new_err(#msg)) } - let slice = s.as_slice(); Ok(#self_ty(#fields)) ) } diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 032935d5776..aca7e7d46c7 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -234,16 +234,31 @@ fn impl_methods_inventory(cls: &syn::Ident) -> TokenStream { } } -/// Implement `HasProtoRegistry` for the class for lazy protocol initialization. -fn impl_proto_registry(cls: &syn::Ident) -> TokenStream { +/// Implement `HasProtoInventory` for the class for lazy protocol initialization. +fn impl_proto_inventory(cls: &syn::Ident) -> TokenStream { + // Try to build a unique type for better error messages + let name = format!("Pyo3ProtoInventoryFor{}", cls); + let inventory_cls = syn::Ident::new(&name, Span::call_site()); + quote! { - impl pyo3::class::proto_methods::HasProtoRegistry for #cls { - fn registry() -> &'static pyo3::class::proto_methods::PyProtoRegistry { - static REGISTRY: pyo3::class::proto_methods::PyProtoRegistry - = pyo3::class::proto_methods::PyProtoRegistry::new(); - ®ISTRY + #[doc(hidden)] + pub struct #inventory_cls { + def: pyo3::class::proto_methods::PyProtoMethodDef, + } + impl pyo3::class::proto_methods::PyProtoInventory for #inventory_cls { + fn new(def: pyo3::class::proto_methods::PyProtoMethodDef) -> Self { + Self { def } } + fn get(&'static self) -> &'static pyo3::class::proto_methods::PyProtoMethodDef { + &self.def + } + } + + impl pyo3::class::proto_methods::HasProtoInventory for #cls { + type ProtoMethods = #inventory_cls; } + + pyo3::inventory::collect!(#inventory_cls); } } @@ -351,7 +366,7 @@ fn impl_class( }; let impl_inventory = impl_methods_inventory(&cls); - let impl_proto_registry = impl_proto_registry(&cls); + let impl_proto_inventory = impl_proto_inventory(&cls); let base = &attr.base; let flags = &attr.flags; @@ -440,7 +455,7 @@ fn impl_class( #impl_inventory - #impl_proto_registry + #impl_proto_inventory #extra diff --git a/pyo3-derive-backend/src/pyproto.rs b/pyo3-derive-backend/src/pyproto.rs index 7c6cbf1a7ae..249301af0cc 100644 --- a/pyo3-derive-backend/src/pyproto.rs +++ b/pyo3-derive-backend/src/pyproto.rs @@ -106,16 +106,16 @@ fn impl_proto_impl( } } } - let inventory_submission = inventory_submission(py_methods, ty); - let slot_initialization = slot_initialization(method_names, ty, proto)?; + let normal_methods = submit_normal_methods(py_methods, ty); + let protocol_methods = submit_protocol_methods(method_names, ty, proto)?; Ok(quote! { #trait_impls - #inventory_submission - #slot_initialization + #normal_methods + #protocol_methods }) } -fn inventory_submission(py_methods: Vec, ty: &syn::Type) -> TokenStream { +fn submit_normal_methods(py_methods: Vec, ty: &syn::Type) -> TokenStream { if py_methods.is_empty() { return quote! {}; } @@ -129,42 +129,49 @@ fn inventory_submission(py_methods: Vec, ty: &syn::Type) -> TokenSt } } -fn slot_initialization( +fn submit_protocol_methods( method_names: HashSet, ty: &syn::Type, proto: &defs::Proto, ) -> syn::Result { - // Collect initializers - let mut initializers: Vec = vec![]; - for setter in proto.setters(method_names) { - // Add slot methods to PyProtoRegistry - let set = syn::Ident::new(setter, Span::call_site()); - initializers.push(quote! { table.#set::<#ty>(); }); + if proto.extension_trait == "" { + return Ok(quote! {}); } - if initializers.is_empty() { + let ext_trait: syn::Path = syn::parse_str(proto.extension_trait)?; + let mut tokens = vec![]; + if proto.name == "Buffer" { + // For buffer, we construct `PyProtoMethods` from PyBufferProcs + tokens.push(quote! { + let mut proto_methods = pyo3::ffi::PyBufferProcs::default(); + }); + for getter in proto.slot_getters(method_names) { + let get = syn::Ident::new(getter, Span::call_site()); + let field = syn::Ident::new(&format!("bf_{}", &getter[4..]), Span::call_site()); + tokens.push(quote! { proto_methods.#field = Some(<#ty as #ext_trait>::#get()); }); + } + } else { + // For other protocols, we construct `PyProtoMethods` from Vec + tokens.push(quote! { let mut proto_methods = vec![]; }); + for getter in proto.slot_getters(method_names) { + let get = syn::Ident::new(getter, Span::call_site()); + tokens.push(quote! { + let slot = <#ty as #ext_trait>::#get(); + proto_methods.push(pyo3::ffi::PyType_Slot { slot: slot.0, pfunc: slot.1 as _ }); + }); + } + }; + if tokens.len() <= 1 { return Ok(quote! {}); } - let table: syn::Path = syn::parse_str(proto.slot_table)?; - let set = syn::Ident::new(proto.set_slot_table, Span::call_site()); - let ty_hash = typename_hash(ty); - let init = syn::Ident::new( - &format!("__init_{}_{}", proto.name, ty_hash), - Span::call_site(), - ); Ok(quote! { - #[allow(non_snake_case)] - #[pyo3::ctor::ctor] - fn #init() { - let mut table = #table::default(); - #(#initializers)* - <#ty as pyo3::class::proto_methods::HasProtoRegistry>::registry().#set(table); + pyo3::inventory::submit! { + #![crate = pyo3] { + type Inventory = + <#ty as pyo3::class::proto_methods::HasProtoInventory>::ProtoMethods; + ::new( + { #(#tokens)* proto_methods.into() } + ) + } } }) } - -fn typename_hash(ty: &syn::Type) -> u64 { - use std::hash::{Hash, Hasher}; - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - ty.hash(&mut hasher); - hasher.finish() -} diff --git a/src/class/basic.rs b/src/class/basic.rs index e082405a882..505946d59ef 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -8,6 +8,7 @@ //! Parts of the documentation are copied from the respective methods from the //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) +use super::proto_methods::TypedSlot; use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput}; use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject, PyResult}; use std::os::raw::c_int; @@ -133,153 +134,150 @@ pub trait PyObjectRichcmpProtocol<'p>: PyObjectProtocol<'p> { type Result: IntoPyCallbackOutput; } -/// All FFI functions for basic protocols. -#[derive(Default)] -pub struct PyObjectMethods { - pub tp_str: Option, - pub tp_repr: Option, - pub tp_hash: Option, - pub tp_getattro: Option, - pub tp_richcompare: Option, - pub tp_setattro: Option, - pub nb_bool: Option, -} - +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyObjectMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_str = self.tp_str; - type_object.tp_repr = self.tp_repr; - type_object.tp_hash = self.tp_hash; - type_object.tp_getattro = self.tp_getattro; - type_object.tp_richcompare = self.tp_richcompare; - type_object.tp_setattro = self.tp_setattro; - } - // Set functions used by `#[pyproto]`. - pub fn set_str(&mut self) - where - T: for<'p> PyObjectStrProtocol<'p>, - { - self.tp_str = py_unary_func!(PyObjectStrProtocol, T::__str__); - } - pub fn set_repr(&mut self) - where - T: for<'p> PyObjectReprProtocol<'p>, - { - self.tp_repr = py_unary_func!(PyObjectReprProtocol, T::__repr__); - } - pub fn set_hash(&mut self) +pub trait PyBasicSlots { + fn get_str() -> TypedSlot where - T: for<'p> PyObjectHashProtocol<'p>, + Self: for<'p> PyObjectStrProtocol<'p>, { - self.tp_hash = py_unary_func!(PyObjectHashProtocol, T::__hash__, ffi::Py_hash_t); + TypedSlot( + ffi::Py_tp_str, + py_unary_func!(PyObjectStrProtocol, Self::__str__), + ) } - pub fn set_getattr(&mut self) + + fn get_repr() -> TypedSlot where - T: for<'p> PyObjectGetAttrProtocol<'p>, + Self: for<'p> PyObjectReprProtocol<'p>, { - self.tp_getattro = tp_getattro::(); + TypedSlot( + ffi::Py_tp_repr, + py_unary_func!(PyObjectReprProtocol, Self::__repr__), + ) } - pub fn set_richcompare(&mut self) + + fn get_hash() -> TypedSlot where - T: for<'p> PyObjectRichcmpProtocol<'p>, + Self: for<'p> PyObjectHashProtocol<'p>, { - self.tp_richcompare = tp_richcompare::(); + TypedSlot( + ffi::Py_tp_hash, + py_unary_func!(PyObjectHashProtocol, Self::__hash__, ffi::Py_hash_t), + ) } - pub fn set_setattr(&mut self) + + fn get_getattr() -> TypedSlot where - T: for<'p> PyObjectSetAttrProtocol<'p>, + Self: for<'p> PyObjectGetAttrProtocol<'p>, { - self.tp_setattro = py_func_set!(PyObjectSetAttrProtocol, T, __setattr__); + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + arg: *mut ffi::PyObject, + ) -> *mut ffi::PyObject + where + T: for<'p> PyObjectGetAttrProtocol<'p>, + { + crate::callback_body!(py, { + // Behave like python's __getattr__ (as opposed to __getattribute__) and check + // for existing fields and methods first + let existing = ffi::PyObject_GenericGetAttr(slf, arg); + if existing.is_null() { + // PyObject_HasAttr also tries to get an object and clears the error if it fails + ffi::PyErr_Clear(); + } else { + return Ok(existing); + } + + let slf = py.from_borrowed_ptr::>(slf); + let arg = py.from_borrowed_ptr::(arg); + call_ref!(slf, __getattr__, arg).convert(py) + }) + } + TypedSlot(ffi::Py_tp_getattro, wrap::) } - pub fn set_delattr(&mut self) + + fn get_richcmp() -> TypedSlot where - T: for<'p> PyObjectDelAttrProtocol<'p>, + Self: for<'p> PyObjectRichcmpProtocol<'p>, { - self.tp_setattro = py_func_del!(PyObjectDelAttrProtocol, T, __delattr__); + fn extract_op(op: c_int) -> PyResult { + match op { + ffi::Py_LT => Ok(CompareOp::Lt), + ffi::Py_LE => Ok(CompareOp::Le), + ffi::Py_EQ => Ok(CompareOp::Eq), + ffi::Py_NE => Ok(CompareOp::Ne), + ffi::Py_GT => Ok(CompareOp::Gt), + ffi::Py_GE => Ok(CompareOp::Ge), + _ => Err(exceptions::PyValueError::new_err( + "tp_richcompare called with invalid comparison operator", + )), + } + } + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + arg: *mut ffi::PyObject, + op: c_int, + ) -> *mut ffi::PyObject + where + T: for<'p> PyObjectRichcmpProtocol<'p>, + { + crate::callback_body!(py, { + let slf = py.from_borrowed_ptr::>(slf); + let arg = extract_or_return_not_implemented!(py, arg); + let op = extract_op(op)?; + + slf.try_borrow()?.__richcmp__(arg, op).convert(py) + }) + } + TypedSlot(ffi::Py_tp_richcompare, wrap::) } - pub fn set_setdelattr(&mut self) + + fn get_setattr() -> TypedSlot where - T: for<'p> PyObjectSetAttrProtocol<'p> + for<'p> PyObjectDelAttrProtocol<'p>, + Self: for<'p> PyObjectSetAttrProtocol<'p>, { - self.tp_setattro = py_func_set_del!( - PyObjectSetAttrProtocol, - PyObjectDelAttrProtocol, - T, - __setattr__, - __delattr__ + TypedSlot( + ffi::Py_tp_setattro, + py_func_set!(PyObjectSetAttrProtocol, Self::__setattr__), ) } - pub fn set_bool(&mut self) + + fn get_delattr() -> TypedSlot where - T: for<'p> PyObjectBoolProtocol<'p>, + Self: for<'p> PyObjectDelAttrProtocol<'p>, { - self.nb_bool = py_unary_func!(PyObjectBoolProtocol, T::__bool__, c_int); + TypedSlot( + ffi::Py_tp_setattro, + py_func_del!(PyObjectDelAttrProtocol, Self::__delattr__), + ) } -} -fn tp_getattro() -> Option -where - T: for<'p> PyObjectGetAttrProtocol<'p>, -{ - unsafe extern "C" fn wrap( - slf: *mut ffi::PyObject, - arg: *mut ffi::PyObject, - ) -> *mut ffi::PyObject + fn get_setdelattr() -> TypedSlot where - T: for<'p> PyObjectGetAttrProtocol<'p>, + Self: for<'p> PyObjectSetAttrProtocol<'p> + for<'p> PyObjectDelAttrProtocol<'p>, { - crate::callback_body!(py, { - // Behave like python's __getattr__ (as opposed to __getattribute__) and check - // for existing fields and methods first - let existing = ffi::PyObject_GenericGetAttr(slf, arg); - if existing.is_null() { - // PyObject_HasAttr also tries to get an object and clears the error if it fails - ffi::PyErr_Clear(); - } else { - return Ok(existing); - } - - let slf = py.from_borrowed_ptr::>(slf); - let arg = py.from_borrowed_ptr::(arg); - call_ref!(slf, __getattr__, arg).convert(py) - }) + TypedSlot( + ffi::Py_tp_setattro, + py_func_set_del!( + PyObjectSetAttrProtocol, + PyObjectDelAttrProtocol, + Self, + __setattr__, + __delattr__ + ), + ) } - Some(wrap::) -} -fn tp_richcompare() -> Option -where - T: for<'p> PyObjectRichcmpProtocol<'p>, -{ - fn extract_op(op: c_int) -> PyResult { - match op { - ffi::Py_LT => Ok(CompareOp::Lt), - ffi::Py_LE => Ok(CompareOp::Le), - ffi::Py_EQ => Ok(CompareOp::Eq), - ffi::Py_NE => Ok(CompareOp::Ne), - ffi::Py_GT => Ok(CompareOp::Gt), - ffi::Py_GE => Ok(CompareOp::Ge), - _ => Err(exceptions::PyValueError::new_err( - "tp_richcompare called with invalid comparison operator", - )), - } - } - unsafe extern "C" fn wrap( - slf: *mut ffi::PyObject, - arg: *mut ffi::PyObject, - op: c_int, - ) -> *mut ffi::PyObject + fn get_bool() -> TypedSlot where - T: for<'p> PyObjectRichcmpProtocol<'p>, + Self: for<'p> PyObjectBoolProtocol<'p>, { - crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::>(slf); - let arg = extract_or_return_not_implemented!(py, arg); - let op = extract_op(op)?; - - slf.try_borrow()?.__richcmp__(arg, op).convert(py) - }) + TypedSlot( + ffi::Py_nb_bool, + py_unary_func!(PyObjectBoolProtocol, Self::__bool__, c_int), + ) } - Some(wrap::) } + +impl<'p, T> PyBasicSlots for T where T: PyObjectProtocol<'p> {} diff --git a/src/class/buffer.rs b/src/class/buffer.rs index f2977274b9f..89b0a880bbe 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -5,10 +5,7 @@ //! For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html) //! c-api use crate::callback::IntoPyCallbackOutput; -use crate::{ - ffi::{self, PyBufferProcs}, - PyCell, PyClass, PyRefMut, -}; +use crate::{ffi, PyCell, PyClass, PyRefMut}; use std::os::raw::c_int; /// Buffer protocol interface @@ -40,55 +37,46 @@ pub trait PyBufferReleaseBufferProtocol<'p>: PyBufferProtocol<'p> { type Result: IntoPyCallbackOutput<()>; } -/// Set functions used by `#[pyproto]`. +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyBufferProcs { - pub fn set_getbuffer(&mut self) +pub trait PyBufferSlots { + fn get_getbuffer() -> ffi::getbufferproc where - T: for<'p> PyBufferGetBufferProtocol<'p>, + Self: for<'p> PyBufferGetBufferProtocol<'p>, { - self.bf_getbuffer = bf_getbuffer::(); - } - pub fn set_releasebuffer(&mut self) - where - T: for<'p> PyBufferReleaseBufferProtocol<'p>, - { - self.bf_releasebuffer = bf_releasebuffer::(); - } -} + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + arg1: *mut ffi::Py_buffer, + arg2: c_int, + ) -> c_int + where + T: for<'p> PyBufferGetBufferProtocol<'p>, + { + crate::callback_body!(py, { + let slf = py.from_borrowed_ptr::>(slf); + T::bf_getbuffer(slf.try_borrow_mut()?, arg1, arg2).convert(py) + }) + } -fn bf_getbuffer() -> Option -where - T: for<'p> PyBufferGetBufferProtocol<'p>, -{ - unsafe extern "C" fn wrap( - slf: *mut ffi::PyObject, - arg1: *mut ffi::Py_buffer, - arg2: c_int, - ) -> c_int - where - T: for<'p> PyBufferGetBufferProtocol<'p>, - { - crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::>(slf); - T::bf_getbuffer(slf.try_borrow_mut()?, arg1, arg2).convert(py) - }) + wrap:: } - Some(wrap::) -} -fn bf_releasebuffer() -> Option -where - T: for<'p> PyBufferReleaseBufferProtocol<'p>, -{ - unsafe extern "C" fn wrap(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer) + fn get_releasebuffer() -> ffi::releasebufferproc where - T: for<'p> PyBufferReleaseBufferProtocol<'p>, + Self: for<'p> PyBufferReleaseBufferProtocol<'p>, { - crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::>(slf); - T::bf_releasebuffer(slf.try_borrow_mut()?, arg1).convert(py) - }) + unsafe extern "C" fn wrap(slf: *mut ffi::PyObject, arg1: *mut ffi::Py_buffer) + where + T: for<'p> PyBufferReleaseBufferProtocol<'p>, + { + crate::callback_body!(py, { + let slf = py.from_borrowed_ptr::>(slf); + T::bf_releasebuffer(slf.try_borrow_mut()?, arg1).convert(py) + }) + } + + wrap:: } - Some(wrap::) } + +impl<'p, T> PyBufferSlots for T where T: PyBufferProtocol<'p> {} diff --git a/src/class/descr.rs b/src/class/descr.rs index 29cfdcfb4e0..036ff881990 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -5,6 +5,7 @@ //! [Python information]( //! https://docs.python.org/3/reference/datamodel.html#implementing-descriptors) +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::types::PyAny; use crate::{ffi, FromPyObject, PyClass, PyObject}; @@ -70,29 +71,28 @@ pub trait PyDescrSetNameProtocol<'p>: PyDescrProtocol<'p> { type Result: IntoPyCallbackOutput<()>; } -/// All FFI functions for description protocols. -#[derive(Default)] -pub struct PyDescrMethods { - pub tp_descr_get: Option, - pub tp_descr_set: Option, -} - +/// Extension trait for our proc-macro backend. #[doc(hidden)] -impl PyDescrMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_descr_get = self.tp_descr_get; - type_object.tp_descr_set = self.tp_descr_set; - } - pub fn set_descr_get(&mut self) +pub trait PyDescrSlots { + fn get_descr_get() -> TypedSlot where - T: for<'p> PyDescrGetProtocol<'p>, + Self: for<'p> PyDescrGetProtocol<'p>, { - self.tp_descr_get = py_ternarys_func!(PyDescrGetProtocol, T::__get__); + TypedSlot( + ffi::Py_tp_descr_get, + py_ternarys_func!(PyDescrGetProtocol, Self::__get__), + ) } - pub fn set_descr_set(&mut self) + + fn get_descr_set() -> TypedSlot where - T: for<'p> PyDescrSetProtocol<'p>, + Self: for<'p> PyDescrSetProtocol<'p>, { - self.tp_descr_set = py_ternarys_func!(PyDescrSetProtocol, T::__set__, c_int); + TypedSlot( + ffi::Py_tp_descr_set, + py_ternarys_func!(PyDescrSetProtocol, Self::__set__, c_int), + ) } } + +impl<'p, T> PyDescrSlots for T where T: PyDescrProtocol<'p> {} diff --git a/src/class/gc.rs b/src/class/gc.rs index f639b8dcc35..9d8db1e096e 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,6 +3,7 @@ //! Python GC support //! +use super::proto_methods::TypedSlot; use crate::{ffi, AsPyPointer, PyCell, PyClass, Python}; use std::os::raw::{c_int, c_void}; @@ -18,37 +19,67 @@ pub trait PyGCProtocol<'p>: PyClass { pub trait PyGCTraverseProtocol<'p>: PyGCProtocol<'p> {} pub trait PyGCClearProtocol<'p>: PyGCProtocol<'p> {} -/// All FFI functions for gc protocols. -#[derive(Default)] -pub struct PyGCMethods { - pub tp_traverse: Option, - pub tp_clear: Option, -} - +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyGCMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_traverse = self.tp_traverse; - type_object.tp_clear = self.tp_clear; - } - - pub fn set_traverse(&mut self) +pub trait PyGCSlots { + fn get_traverse() -> TypedSlot where - T: for<'p> PyGCTraverseProtocol<'p>, + Self: for<'p> PyGCTraverseProtocol<'p>, { - self.tp_traverse = tp_traverse::(); + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + visit: ffi::visitproc, + arg: *mut c_void, + ) -> c_int + where + T: for<'p> PyGCTraverseProtocol<'p>, + { + let pool = crate::GILPool::new(); + let py = pool.python(); + let slf = py.from_borrowed_ptr::>(slf); + + let visit = PyVisit { + visit, + arg, + _py: py, + }; + let borrow = slf.try_borrow(); + if let Ok(borrow) = borrow { + match borrow.__traverse__(visit) { + Ok(()) => 0, + Err(PyTraverseError(code)) => code, + } + } else { + 0 + } + } + + TypedSlot(ffi::Py_tp_traverse, wrap::) } - pub fn set_clear(&mut self) + fn get_clear() -> TypedSlot where - T: for<'p> PyGCClearProtocol<'p>, + Self: for<'p> PyGCClearProtocol<'p>, { - self.tp_clear = tp_clear::(); + unsafe extern "C" fn wrap(slf: *mut ffi::PyObject) -> c_int + where + T: for<'p> PyGCClearProtocol<'p>, + { + let pool = crate::GILPool::new(); + let slf = pool.python().from_borrowed_ptr::>(slf); + + slf.borrow_mut().__clear__(); + 0 + } + + TypedSlot(ffi::Py_tp_clear, wrap::) } } +impl<'p, T> PyGCSlots for T where T: PyGCProtocol<'p> {} + /// Object visitor for GC. -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct PyVisit<'p> { visit: ffi::visitproc, arg: *mut c_void, @@ -72,56 +103,3 @@ impl<'p> PyVisit<'p> { } } } - -fn tp_traverse() -> Option -where - T: for<'p> PyGCTraverseProtocol<'p>, -{ - unsafe extern "C" fn tp_traverse( - slf: *mut ffi::PyObject, - visit: ffi::visitproc, - arg: *mut c_void, - ) -> c_int - where - T: for<'p> PyGCTraverseProtocol<'p>, - { - let pool = crate::GILPool::new(); - let py = pool.python(); - let slf = py.from_borrowed_ptr::>(slf); - - let visit = PyVisit { - visit, - arg, - _py: py, - }; - let borrow = slf.try_borrow(); - if let Ok(borrow) = borrow { - match borrow.__traverse__(visit) { - Ok(()) => 0, - Err(PyTraverseError(code)) => code, - } - } else { - 0 - } - } - - Some(tp_traverse::) -} - -fn tp_clear() -> Option -where - T: for<'p> PyGCClearProtocol<'p>, -{ - unsafe extern "C" fn tp_clear(slf: *mut ffi::PyObject) -> c_int - where - T: for<'p> PyGCClearProtocol<'p>, - { - let pool = crate::GILPool::new(); - let py = pool.python(); - let slf = py.from_borrowed_ptr::>(slf); - - slf.borrow_mut().__clear__(); - 0 - } - Some(tp_clear::) -} diff --git a/src/class/iter.rs b/src/class/iter.rs index 057eea57807..c08bef0a215 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -2,6 +2,7 @@ //! Python Iterator Interface. //! Trait and support implementation for implementing iterators +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::derive_utils::TryFromPyCell; use crate::err::PyResult; @@ -71,32 +72,31 @@ pub trait PyIterNextProtocol<'p>: PyIterProtocol<'p> { type Result: IntoPyCallbackOutput; } -#[derive(Default)] -pub struct PyIterMethods { - pub tp_iter: Option, - pub tp_iternext: Option, -} - +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl PyIterMethods { - pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) { - type_object.tp_iter = self.tp_iter; - type_object.tp_iternext = self.tp_iternext; - } - pub fn set_iter(&mut self) +pub trait PyIterSlots { + fn get_iter() -> TypedSlot where - T: for<'p> PyIterIterProtocol<'p>, + Self: for<'p> PyIterIterProtocol<'p>, { - self.tp_iter = py_unarys_func!(PyIterIterProtocol, T::__iter__); + TypedSlot( + ffi::Py_tp_iter, + py_unarys_func!(PyIterIterProtocol, Self::__iter__), + ) } - pub fn set_iternext(&mut self) + fn get_iternext() -> TypedSlot where - T: for<'p> PyIterNextProtocol<'p>, + Self: for<'p> PyIterNextProtocol<'p>, { - self.tp_iternext = py_unarys_func!(PyIterNextProtocol, T::__next__); + TypedSlot( + ffi::Py_tp_iternext, + py_unarys_func!(PyIterNextProtocol, Self::__next__), + ) } } +impl<'p, T> PyIterSlots for T where T: PyIterProtocol<'p> {} + /// Output of `__next__` which can either `yield` the next value in the iteration, or /// `return` a value to raise `StopIteration` in Python. /// diff --git a/src/class/macros.rs b/src/class/macros.rs index 49db13c775b..98c0a47da74 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -11,7 +11,7 @@ macro_rules! py_unary_func { $call!(slf, $f).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; // Use call_ref! by default ($trait:ident, $class:ident :: $f:ident, $ret_type:ty) => { @@ -34,10 +34,10 @@ macro_rules! py_unarys_func { >::try_from_pycell(slf) .map_err(|e| e.into())?; - $class::$f(borrow).convert(py) + T::$f(borrow).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -60,7 +60,7 @@ macro_rules! py_binary_func { $call!(slf, $f, arg).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; ($trait:ident, $class:ident :: $f:ident, $return:ty) => { py_binary_func!($trait, $class::$f, $return, call_ref) @@ -82,10 +82,10 @@ macro_rules! py_binary_num_func { $crate::callback_body!(py, { let lhs = py.from_borrowed_ptr::<$crate::PyAny>(lhs); let rhs = extract_or_return_not_implemented!(py, rhs); - $class::$f(lhs.extract()?, rhs).convert(py) + T::$f(lhs.extract()?, rhs).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -102,10 +102,10 @@ macro_rules! py_binary_reversed_num_func { // Swap lhs <-> rhs let slf: &$crate::PyCell = extract_or_return_not_implemented!(py, rhs); let arg = extract_or_return_not_implemented!(py, lhs); - $class::$f(&*slf.try_borrow()?, arg).convert(py) + T::$f(&*slf.try_borrow()?, arg).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -123,17 +123,17 @@ macro_rules! py_binary_fallback_num_func { let rhs = py.from_borrowed_ptr::<$crate::PyAny>(rhs); // First, try the left hand method (e.g., __add__) match (lhs.extract(), rhs.extract()) { - (Ok(l), Ok(r)) => $class::$lop(l, r).convert(py), + (Ok(l), Ok(r)) => T::$lop(l, r).convert(py), _ => { // Next, try the right hand method (e.g., __radd__) let slf: &$crate::PyCell = extract_or_return_not_implemented!(rhs); let arg = extract_or_return_not_implemented!(lhs); - $class::$rop(&*slf.try_borrow()?, arg).convert(py) + T::$rop(&*slf.try_borrow()?, arg).convert(py) } } }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -155,7 +155,7 @@ macro_rules! py_binary_self_func { Ok::<_, $crate::err::PyErr>(slf) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -177,7 +177,7 @@ macro_rules! py_ssizearg_func { $call!(slf, $f; arg.into()).convert(py) }) } - Some(wrap::<$class>) + wrap::<$class> }}; } @@ -203,11 +203,11 @@ macro_rules! py_ternarys_func { .from_borrowed_ptr::<$crate::types::PyAny>(arg2) .extract()?; - $class::$f(slf, arg1, arg2).convert(py) + T::$f(slf, arg1, arg2).convert(py) }) } - Some(wrap::) + wrap::<$class> }}; ($trait:ident, $class:ident :: $f:ident) => { py_ternarys_func!($trait, $class::$f, *mut $crate::ffi::PyObject); @@ -215,8 +215,8 @@ macro_rules! py_ternarys_func { } macro_rules! py_func_set { - ($trait_name:ident, $generic:ident, $fn_set:ident) => {{ - unsafe extern "C" fn wrap<$generic>( + ($trait_name:ident, $class:ident :: $fn_set:ident) => {{ + unsafe extern "C" fn wrap( slf: *mut $crate::ffi::PyObject, name: *mut $crate::ffi::PyObject, value: *mut $crate::ffi::PyObject, @@ -225,12 +225,12 @@ macro_rules! py_func_set { T: for<'p> $trait_name<'p>, { $crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); if value.is_null() { Err($crate::exceptions::PyNotImplementedError::new_err(format!( "Subscript deletion not supported by {:?}", - stringify!($generic) + stringify!($class) ))) } else { let name = py.from_borrowed_ptr::<$crate::PyAny>(name); @@ -240,23 +240,23 @@ macro_rules! py_func_set { }) } - Some(wrap::<$generic>) + wrap::<$class> }}; } macro_rules! py_func_del { - ($trait_name:ident, $generic:ident, $fn_del:ident) => {{ - unsafe extern "C" fn wrap( + ($trait_name:ident, $class:ident :: $fn_del:ident) => {{ + unsafe extern "C" fn wrap( slf: *mut $crate::ffi::PyObject, name: *mut $crate::ffi::PyObject, value: *mut $crate::ffi::PyObject, ) -> libc::c_int where - U: for<'p> $trait_name<'p>, + T: for<'p> $trait_name<'p>, { $crate::callback_body!(py, { if value.is_null() { - let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let name = py .from_borrowed_ptr::<$crate::types::PyAny>(name) .extract()?; @@ -269,13 +269,13 @@ macro_rules! py_func_del { }) } - Some(wrap::<$generic>) + wrap::<$class> }}; } macro_rules! py_func_set_del { - ($trait1:ident, $trait2:ident, $generic:ident, $fn_set:ident, $fn_del:ident) => {{ - unsafe extern "C" fn wrap<$generic>( + ($trait1:ident, $trait2:ident, $class:ident, $fn_set:ident, $fn_del:ident) => {{ + unsafe extern "C" fn wrap( slf: *mut $crate::ffi::PyObject, name: *mut $crate::ffi::PyObject, value: *mut $crate::ffi::PyObject, @@ -284,7 +284,7 @@ macro_rules! py_func_set_del { T: for<'p> $trait1<'p> + for<'p> $trait2<'p>, { $crate::callback_body!(py, { - let slf = py.from_borrowed_ptr::<$crate::PyCell<$generic>>(slf); + let slf = py.from_borrowed_ptr::<$crate::PyCell>(slf); let name = py.from_borrowed_ptr::<$crate::PyAny>(name); if value.is_null() { @@ -295,7 +295,7 @@ macro_rules! py_func_set_del { } }) } - Some(wrap::<$generic>) + wrap::<$class> }}; } diff --git a/src/class/mapping.rs b/src/class/mapping.rs index a8c30651f9c..df3cfe14828 100644 --- a/src/class/mapping.rs +++ b/src/class/mapping.rs @@ -3,6 +3,7 @@ //! Python Mapping Interface //! Trait and support implementation for implementing mapping support +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::{exceptions, ffi, FromPyObject, PyClass, PyObject}; @@ -72,42 +73,64 @@ pub trait PyMappingReversedProtocol<'p>: PyMappingProtocol<'p> { type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl ffi::PyMappingMethods { - pub fn set_length(&mut self) +pub trait PyMappingSlots { + fn get_len() -> TypedSlot where - T: for<'p> PyMappingLenProtocol<'p>, + Self: for<'p> PyMappingLenProtocol<'p>, { - self.mp_length = py_len_func!(PyMappingLenProtocol, T::__len__); + TypedSlot( + ffi::Py_mp_length, + py_len_func!(PyMappingLenProtocol, Self::__len__), + ) } - pub fn set_getitem(&mut self) + + fn get_getitem() -> TypedSlot where - T: for<'p> PyMappingGetItemProtocol<'p>, + Self: for<'p> PyMappingGetItemProtocol<'p>, { - self.mp_subscript = py_binary_func!(PyMappingGetItemProtocol, T::__getitem__); + TypedSlot( + ffi::Py_mp_subscript, + py_binary_func!(PyMappingGetItemProtocol, Self::__getitem__), + ) } - pub fn set_setitem(&mut self) + + fn get_setitem() -> TypedSlot where - T: for<'p> PyMappingSetItemProtocol<'p>, + Self: for<'p> PyMappingSetItemProtocol<'p>, { - self.mp_ass_subscript = py_func_set!(PyMappingSetItemProtocol, T, __setitem__); + TypedSlot( + ffi::Py_mp_ass_subscript, + py_func_set!(PyMappingSetItemProtocol, Self::__setitem__), + ) } - pub fn set_delitem(&mut self) + + fn get_delitem() -> TypedSlot where - T: for<'p> PyMappingDelItemProtocol<'p>, + Self: for<'p> PyMappingDelItemProtocol<'p>, { - self.mp_ass_subscript = py_func_del!(PyMappingDelItemProtocol, T, __delitem__); + TypedSlot( + ffi::Py_mp_ass_subscript, + py_func_del!(PyMappingDelItemProtocol, Self::__delitem__), + ) } - pub fn set_setdelitem(&mut self) + + fn get_setdelitem() -> TypedSlot where - T: for<'p> PyMappingSetItemProtocol<'p> + for<'p> PyMappingDelItemProtocol<'p>, + Self: for<'p> PyMappingSetItemProtocol<'p> + for<'p> PyMappingDelItemProtocol<'p>, { - self.mp_ass_subscript = py_func_set_del!( - PyMappingSetItemProtocol, - PyMappingDelItemProtocol, - T, - __setitem__, - __delitem__ - ); + TypedSlot( + ffi::Py_mp_ass_subscript, + py_func_set_del!( + PyMappingSetItemProtocol, + PyMappingDelItemProtocol, + Self, + __setitem__, + __delitem__ + ), + ) } } + +impl<'p, T> PyMappingSlots for T where T: PyMappingProtocol<'p> {} diff --git a/src/class/number.rs b/src/class/number.rs index 568ed38b780..cdcea6b0a58 100644 --- a/src/class/number.rs +++ b/src/class/number.rs @@ -2,7 +2,7 @@ //! Python Number Interface //! Trait and support implementation for implementing number protocol - +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::err::PyErr; use crate::{ffi, FromPyObject, PyClass, PyObject}; @@ -579,116 +579,164 @@ pub trait PyNumberIndexProtocol<'p>: PyNumberProtocol<'p> { type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl ffi::PyNumberMethods { - pub(crate) fn from_nb_bool(nb_bool: ffi::inquiry) -> *mut Self { - let mut nm = ffi::PyNumberMethods_INIT; - nm.nb_bool = Some(nb_bool); - Box::into_raw(Box::new(nm)) - } - pub fn set_add_radd(&mut self) +pub trait PyNumberSlots { + fn get_add_radd() -> TypedSlot where - T: for<'p> PyNumberAddProtocol<'p> + for<'p> PyNumberRAddProtocol<'p>, + Self: for<'p> PyNumberAddProtocol<'p> + for<'p> PyNumberRAddProtocol<'p>, { - self.nb_add = py_binary_fallback_num_func!( - T, - PyNumberAddProtocol::__add__, - PyNumberRAddProtocol::__radd__ - ); + TypedSlot( + ffi::Py_nb_add, + py_binary_fallback_num_func!( + Self, + PyNumberAddProtocol::__add__, + PyNumberRAddProtocol::__radd__ + ), + ) } - pub fn set_add(&mut self) + + fn get_add() -> TypedSlot where - T: for<'p> PyNumberAddProtocol<'p>, + Self: for<'p> PyNumberAddProtocol<'p>, { - self.nb_add = py_binary_num_func!(PyNumberAddProtocol, T::__add__); + TypedSlot( + ffi::Py_nb_add, + py_binary_num_func!(PyNumberAddProtocol, Self::__add__), + ) } - pub fn set_radd(&mut self) + + fn get_radd() -> TypedSlot where - T: for<'p> PyNumberRAddProtocol<'p>, + Self: for<'p> PyNumberRAddProtocol<'p>, { - self.nb_add = py_binary_reversed_num_func!(PyNumberRAddProtocol, T::__radd__); + TypedSlot( + ffi::Py_nb_add, + py_binary_reversed_num_func!(PyNumberRAddProtocol, Self::__radd__), + ) } - pub fn set_sub_rsub(&mut self) + + fn get_sub_rsub() -> TypedSlot where - T: for<'p> PyNumberSubProtocol<'p> + for<'p> PyNumberRSubProtocol<'p>, + Self: for<'p> PyNumberSubProtocol<'p> + for<'p> PyNumberRSubProtocol<'p>, { - self.nb_subtract = py_binary_fallback_num_func!( - T, - PyNumberSubProtocol::__sub__, - PyNumberRSubProtocol::__rsub__ - ); + TypedSlot( + ffi::Py_nb_subtract, + py_binary_fallback_num_func!( + Self, + PyNumberSubProtocol::__sub__, + PyNumberRSubProtocol::__rsub__ + ), + ) } - pub fn set_sub(&mut self) + + fn get_sub() -> TypedSlot where - T: for<'p> PyNumberSubProtocol<'p>, + Self: for<'p> PyNumberSubProtocol<'p>, { - self.nb_subtract = py_binary_num_func!(PyNumberSubProtocol, T::__sub__); + TypedSlot( + ffi::Py_nb_subtract, + py_binary_num_func!(PyNumberSubProtocol, Self::__sub__), + ) } - pub fn set_rsub(&mut self) + + fn get_rsub() -> TypedSlot where - T: for<'p> PyNumberRSubProtocol<'p>, + Self: for<'p> PyNumberRSubProtocol<'p>, { - self.nb_subtract = py_binary_reversed_num_func!(PyNumberRSubProtocol, T::__rsub__); + TypedSlot( + ffi::Py_nb_subtract, + py_binary_reversed_num_func!(PyNumberRSubProtocol, Self::__rsub__), + ) } - pub fn set_mul_rmul(&mut self) + + fn get_mul_rmul() -> TypedSlot where - T: for<'p> PyNumberMulProtocol<'p> + for<'p> PyNumberRMulProtocol<'p>, + Self: for<'p> PyNumberMulProtocol<'p> + for<'p> PyNumberRMulProtocol<'p>, { - self.nb_multiply = py_binary_fallback_num_func!( - T, - PyNumberMulProtocol::__mul__, - PyNumberRMulProtocol::__rmul__ - ); + TypedSlot( + ffi::Py_nb_multiply, + py_binary_fallback_num_func!( + Self, + PyNumberMulProtocol::__mul__, + PyNumberRMulProtocol::__rmul__ + ), + ) } - pub fn set_mul(&mut self) + + fn get_mul() -> TypedSlot where - T: for<'p> PyNumberMulProtocol<'p>, + Self: for<'p> PyNumberMulProtocol<'p>, { - self.nb_multiply = py_binary_num_func!(PyNumberMulProtocol, T::__mul__); + TypedSlot( + ffi::Py_nb_multiply, + py_binary_num_func!(PyNumberMulProtocol, Self::__mul__), + ) } - pub fn set_rmul(&mut self) + + fn get_rmul() -> TypedSlot where - T: for<'p> PyNumberRMulProtocol<'p>, + Self: for<'p> PyNumberRMulProtocol<'p>, { - self.nb_multiply = py_binary_reversed_num_func!(PyNumberRMulProtocol, T::__rmul__); + TypedSlot( + ffi::Py_nb_multiply, + py_binary_reversed_num_func!(PyNumberRMulProtocol, Self::__rmul__), + ) } - pub fn set_mod(&mut self) + + fn get_mod() -> TypedSlot where - T: for<'p> PyNumberModProtocol<'p>, + Self: for<'p> PyNumberModProtocol<'p>, { - self.nb_remainder = py_binary_num_func!(PyNumberModProtocol, T::__mod__); + TypedSlot( + ffi::Py_nb_remainder, + py_binary_num_func!(PyNumberModProtocol, Self::__mod__), + ) } - pub fn set_divmod_rdivmod(&mut self) + + fn get_divmod_rdivmod() -> TypedSlot where - T: for<'p> PyNumberDivmodProtocol<'p> + for<'p> PyNumberRDivmodProtocol<'p>, + Self: for<'p> PyNumberDivmodProtocol<'p> + for<'p> PyNumberRDivmodProtocol<'p>, { - self.nb_divmod = py_binary_fallback_num_func!( - T, - PyNumberDivmodProtocol::__divmod__, - PyNumberRDivmodProtocol::__rdivmod__ - ); + TypedSlot( + ffi::Py_nb_divmod, + py_binary_fallback_num_func!( + Self, + PyNumberDivmodProtocol::__divmod__, + PyNumberRDivmodProtocol::__rdivmod__ + ), + ) } - pub fn set_divmod(&mut self) + + fn get_divmod() -> TypedSlot where - T: for<'p> PyNumberDivmodProtocol<'p>, + Self: for<'p> PyNumberDivmodProtocol<'p>, { - self.nb_divmod = py_binary_num_func!(PyNumberDivmodProtocol, T::__divmod__); + TypedSlot( + ffi::Py_nb_divmod, + py_binary_num_func!(PyNumberDivmodProtocol, Self::__divmod__), + ) } - pub fn set_rdivmod(&mut self) + + fn get_rdivmod() -> TypedSlot where - T: for<'p> PyNumberRDivmodProtocol<'p>, + Self: for<'p> PyNumberRDivmodProtocol<'p>, { - self.nb_divmod = py_binary_reversed_num_func!(PyNumberRDivmodProtocol, T::__rdivmod__); + TypedSlot( + ffi::Py_nb_divmod, + py_binary_reversed_num_func!(PyNumberRDivmodProtocol, Self::__rdivmod__), + ) } - pub fn set_pow_rpow(&mut self) + + fn get_pow_rpow() -> TypedSlot where - T: for<'p> PyNumberPowProtocol<'p> + for<'p> PyNumberRPowProtocol<'p>, + Self: for<'p> PyNumberPowProtocol<'p> + for<'p> PyNumberRPowProtocol<'p>, { unsafe extern "C" fn wrap_pow_and_rpow( - lhs: *mut crate::ffi::PyObject, - rhs: *mut crate::ffi::PyObject, - modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + lhs: *mut ffi::PyObject, + rhs: *mut ffi::PyObject, + modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberPowProtocol<'p> + for<'p> PyNumberRPowProtocol<'p>, { @@ -709,17 +757,19 @@ impl ffi::PyNumberMethods { } }) } - self.nb_power = Some(wrap_pow_and_rpow::); + + TypedSlot(ffi::Py_nb_power, wrap_pow_and_rpow::) } - pub fn set_pow(&mut self) + + fn get_pow() -> TypedSlot where - T: for<'p> PyNumberPowProtocol<'p>, + Self: for<'p> PyNumberPowProtocol<'p>, { unsafe extern "C" fn wrap_pow( - lhs: *mut crate::ffi::PyObject, - rhs: *mut crate::ffi::PyObject, - modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + lhs: *mut ffi::PyObject, + rhs: *mut ffi::PyObject, + modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberPowProtocol<'p>, { @@ -730,17 +780,19 @@ impl ffi::PyNumberMethods { T::__pow__(lhs, rhs, modulo).convert(py) }) } - self.nb_power = Some(wrap_pow::); + + TypedSlot(ffi::Py_nb_power, wrap_pow::) } - pub fn set_rpow(&mut self) + + fn get_rpow() -> TypedSlot where - T: for<'p> PyNumberRPowProtocol<'p>, + Self: for<'p> PyNumberRPowProtocol<'p>, { unsafe extern "C" fn wrap_rpow( - arg: *mut crate::ffi::PyObject, - slf: *mut crate::ffi::PyObject, - modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + arg: *mut ffi::PyObject, + slf: *mut ffi::PyObject, + modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberRPowProtocol<'p>, { @@ -751,189 +803,291 @@ impl ffi::PyNumberMethods { slf.try_borrow()?.__rpow__(arg, modulo).convert(py) }) } - self.nb_power = Some(wrap_rpow::); + + TypedSlot(ffi::Py_nb_power, wrap_rpow::) } - pub fn set_neg(&mut self) + + fn get_neg() -> TypedSlot where - T: for<'p> PyNumberNegProtocol<'p>, + Self: for<'p> PyNumberNegProtocol<'p>, { - self.nb_negative = py_unary_func!(PyNumberNegProtocol, T::__neg__); + TypedSlot( + ffi::Py_nb_negative, + py_unary_func!(PyNumberNegProtocol, Self::__neg__), + ) } - pub fn set_pos(&mut self) + + fn get_pos() -> TypedSlot where - T: for<'p> PyNumberPosProtocol<'p>, + Self: for<'p> PyNumberPosProtocol<'p>, { - self.nb_positive = py_unary_func!(PyNumberPosProtocol, T::__pos__); + TypedSlot( + ffi::Py_nb_positive, + py_unary_func!(PyNumberPosProtocol, Self::__pos__), + ) } - pub fn set_abs(&mut self) + + fn get_abs() -> TypedSlot where - T: for<'p> PyNumberAbsProtocol<'p>, + Self: for<'p> PyNumberAbsProtocol<'p>, { - self.nb_absolute = py_unary_func!(PyNumberAbsProtocol, T::__abs__); + TypedSlot( + ffi::Py_nb_absolute, + py_unary_func!(PyNumberAbsProtocol, Self::__abs__), + ) } - pub fn set_invert(&mut self) + + fn get_invert() -> TypedSlot where - T: for<'p> PyNumberInvertProtocol<'p>, + Self: for<'p> PyNumberInvertProtocol<'p>, { - self.nb_invert = py_unary_func!(PyNumberInvertProtocol, T::__invert__); + TypedSlot( + ffi::Py_nb_invert, + py_unary_func!(PyNumberInvertProtocol, Self::__invert__), + ) } - pub fn set_lshift_rlshift(&mut self) + + fn get_lshift_rlshift() -> TypedSlot where - T: for<'p> PyNumberLShiftProtocol<'p> + for<'p> PyNumberRLShiftProtocol<'p>, + Self: for<'p> PyNumberLShiftProtocol<'p> + for<'p> PyNumberRLShiftProtocol<'p>, { - self.nb_lshift = py_binary_fallback_num_func!( - T, - PyNumberLShiftProtocol::__lshift__, - PyNumberRLShiftProtocol::__rlshift__ - ); + TypedSlot( + ffi::Py_nb_lshift, + py_binary_fallback_num_func!( + Self, + PyNumberLShiftProtocol::__lshift__, + PyNumberRLShiftProtocol::__rlshift__ + ), + ) } - pub fn set_lshift(&mut self) + + fn get_lshift() -> TypedSlot where - T: for<'p> PyNumberLShiftProtocol<'p>, + Self: for<'p> PyNumberLShiftProtocol<'p>, { - self.nb_lshift = py_binary_num_func!(PyNumberLShiftProtocol, T::__lshift__); + TypedSlot( + ffi::Py_nb_lshift, + py_binary_num_func!(PyNumberLShiftProtocol, Self::__lshift__), + ) } - pub fn set_rlshift(&mut self) + + fn get_rlshift() -> TypedSlot where - T: for<'p> PyNumberRLShiftProtocol<'p>, + Self: for<'p> PyNumberRLShiftProtocol<'p>, { - self.nb_lshift = py_binary_reversed_num_func!(PyNumberRLShiftProtocol, T::__rlshift__); + TypedSlot( + ffi::Py_nb_lshift, + py_binary_reversed_num_func!(PyNumberRLShiftProtocol, Self::__rlshift__), + ) } - pub fn set_rshift_rrshift(&mut self) + + fn get_rshift_rrshift() -> TypedSlot where - T: for<'p> PyNumberRShiftProtocol<'p> + for<'p> PyNumberRRShiftProtocol<'p>, + Self: for<'p> PyNumberRShiftProtocol<'p> + for<'p> PyNumberRRShiftProtocol<'p>, { - self.nb_rshift = py_binary_fallback_num_func!( - T, - PyNumberRShiftProtocol::__rshift__, - PyNumberRRShiftProtocol::__rrshift__ - ); + TypedSlot( + ffi::Py_nb_rshift, + py_binary_fallback_num_func!( + Self, + PyNumberRShiftProtocol::__rshift__, + PyNumberRRShiftProtocol::__rrshift__ + ), + ) } - pub fn set_rshift(&mut self) + + fn get_rshift() -> TypedSlot where - T: for<'p> PyNumberRShiftProtocol<'p>, + Self: for<'p> PyNumberRShiftProtocol<'p>, { - self.nb_rshift = py_binary_num_func!(PyNumberRShiftProtocol, T::__rshift__); + TypedSlot( + ffi::Py_nb_rshift, + py_binary_num_func!(PyNumberRShiftProtocol, Self::__rshift__), + ) } - pub fn set_rrshift(&mut self) + + fn get_rrshift() -> TypedSlot where - T: for<'p> PyNumberRRShiftProtocol<'p>, + Self: for<'p> PyNumberRRShiftProtocol<'p>, { - self.nb_rshift = py_binary_reversed_num_func!(PyNumberRRShiftProtocol, T::__rrshift__); + TypedSlot( + ffi::Py_nb_rshift, + py_binary_reversed_num_func!(PyNumberRRShiftProtocol, Self::__rrshift__), + ) } - pub fn set_and_rand(&mut self) + + fn get_and_rand() -> TypedSlot where - T: for<'p> PyNumberAndProtocol<'p> + for<'p> PyNumberRAndProtocol<'p>, + Self: for<'p> PyNumberAndProtocol<'p> + for<'p> PyNumberRAndProtocol<'p>, { - self.nb_and = py_binary_fallback_num_func!( - T, - PyNumberAndProtocol::__and__, - PyNumberRAndProtocol::__rand__ - ); + TypedSlot( + ffi::Py_nb_and, + py_binary_fallback_num_func!( + Self, + PyNumberAndProtocol::__and__, + PyNumberRAndProtocol::__rand__ + ), + ) } - pub fn set_and(&mut self) + + fn get_and() -> TypedSlot where - T: for<'p> PyNumberAndProtocol<'p>, + Self: for<'p> PyNumberAndProtocol<'p>, { - self.nb_and = py_binary_num_func!(PyNumberAndProtocol, T::__and__); + TypedSlot( + ffi::Py_nb_and, + py_binary_num_func!(PyNumberAndProtocol, Self::__and__), + ) } - pub fn set_rand(&mut self) + + fn get_rand() -> TypedSlot where - T: for<'p> PyNumberRAndProtocol<'p>, + Self: for<'p> PyNumberRAndProtocol<'p>, { - self.nb_and = py_binary_reversed_num_func!(PyNumberRAndProtocol, T::__rand__); + TypedSlot( + ffi::Py_nb_and, + py_binary_reversed_num_func!(PyNumberRAndProtocol, Self::__rand__), + ) } - pub fn set_xor_rxor(&mut self) + + fn get_xor_rxor() -> TypedSlot where - T: for<'p> PyNumberXorProtocol<'p> + for<'p> PyNumberRXorProtocol<'p>, + Self: for<'p> PyNumberXorProtocol<'p> + for<'p> PyNumberRXorProtocol<'p>, { - self.nb_xor = py_binary_fallback_num_func!( - T, - PyNumberXorProtocol::__xor__, - PyNumberRXorProtocol::__rxor__ - ); + TypedSlot( + ffi::Py_nb_xor, + py_binary_fallback_num_func!( + Self, + PyNumberXorProtocol::__xor__, + PyNumberRXorProtocol::__rxor__ + ), + ) } - pub fn set_xor(&mut self) + + fn get_xor() -> TypedSlot where - T: for<'p> PyNumberXorProtocol<'p>, + Self: for<'p> PyNumberXorProtocol<'p>, { - self.nb_xor = py_binary_num_func!(PyNumberXorProtocol, T::__xor__); + TypedSlot( + ffi::Py_nb_xor, + py_binary_num_func!(PyNumberXorProtocol, Self::__xor__), + ) } - pub fn set_rxor(&mut self) + + fn get_rxor() -> TypedSlot where - T: for<'p> PyNumberRXorProtocol<'p>, + Self: for<'p> PyNumberRXorProtocol<'p>, { - self.nb_xor = py_binary_reversed_num_func!(PyNumberRXorProtocol, T::__rxor__); + TypedSlot( + ffi::Py_nb_xor, + py_binary_reversed_num_func!(PyNumberRXorProtocol, Self::__rxor__), + ) } - pub fn set_or_ror(&mut self) + + fn get_or_ror() -> TypedSlot where - T: for<'p> PyNumberOrProtocol<'p> + for<'p> PyNumberROrProtocol<'p>, + Self: for<'p> PyNumberOrProtocol<'p> + for<'p> PyNumberROrProtocol<'p>, { - self.nb_or = py_binary_fallback_num_func!( - T, - PyNumberOrProtocol::__or__, - PyNumberROrProtocol::__ror__ - ); + TypedSlot( + ffi::Py_nb_or, + py_binary_fallback_num_func!( + Self, + PyNumberOrProtocol::__or__, + PyNumberROrProtocol::__ror__ + ), + ) } - pub fn set_or(&mut self) + + fn get_or() -> TypedSlot where - T: for<'p> PyNumberOrProtocol<'p>, + Self: for<'p> PyNumberOrProtocol<'p>, { - self.nb_or = py_binary_num_func!(PyNumberOrProtocol, T::__or__); + TypedSlot( + ffi::Py_nb_or, + py_binary_num_func!(PyNumberOrProtocol, Self::__or__), + ) } - pub fn set_ror(&mut self) + + fn get_ror() -> TypedSlot where - T: for<'p> PyNumberROrProtocol<'p>, + Self: for<'p> PyNumberROrProtocol<'p>, { - self.nb_or = py_binary_reversed_num_func!(PyNumberROrProtocol, T::__ror__); + TypedSlot( + ffi::Py_nb_or, + py_binary_reversed_num_func!(PyNumberROrProtocol, Self::__ror__), + ) } - pub fn set_int(&mut self) + + fn get_int() -> TypedSlot where - T: for<'p> PyNumberIntProtocol<'p>, + Self: for<'p> PyNumberIntProtocol<'p>, { - self.nb_int = py_unary_func!(PyNumberIntProtocol, T::__int__); + TypedSlot( + ffi::Py_nb_int, + py_unary_func!(PyNumberIntProtocol, Self::__int__), + ) } - pub fn set_float(&mut self) + + fn get_float() -> TypedSlot where - T: for<'p> PyNumberFloatProtocol<'p>, + Self: for<'p> PyNumberFloatProtocol<'p>, { - self.nb_float = py_unary_func!(PyNumberFloatProtocol, T::__float__); + TypedSlot( + ffi::Py_nb_float, + py_unary_func!(PyNumberFloatProtocol, Self::__float__), + ) } - pub fn set_iadd(&mut self) + + fn get_iadd() -> TypedSlot where - T: for<'p> PyNumberIAddProtocol<'p>, + Self: for<'p> PyNumberIAddProtocol<'p>, { - self.nb_inplace_add = py_binary_self_func!(PyNumberIAddProtocol, T::__iadd__); + TypedSlot( + ffi::Py_nb_inplace_add, + py_binary_self_func!(PyNumberIAddProtocol, Self::__iadd__), + ) } - pub fn set_isub(&mut self) + + fn get_isub() -> TypedSlot where - T: for<'p> PyNumberISubProtocol<'p>, + Self: for<'p> PyNumberISubProtocol<'p>, { - self.nb_inplace_subtract = py_binary_self_func!(PyNumberISubProtocol, T::__isub__); + TypedSlot( + ffi::Py_nb_inplace_subtract, + py_binary_self_func!(PyNumberISubProtocol, Self::__isub__), + ) } - pub fn set_imul(&mut self) + + fn get_imul() -> TypedSlot where - T: for<'p> PyNumberIMulProtocol<'p>, + Self: for<'p> PyNumberIMulProtocol<'p>, { - self.nb_inplace_multiply = py_binary_self_func!(PyNumberIMulProtocol, T::__imul__); + TypedSlot( + ffi::Py_nb_inplace_multiply, + py_binary_self_func!(PyNumberIMulProtocol, Self::__imul__), + ) } - pub fn set_imod(&mut self) + + fn get_imod() -> TypedSlot where - T: for<'p> PyNumberIModProtocol<'p>, + Self: for<'p> PyNumberIModProtocol<'p>, { - self.nb_inplace_remainder = py_binary_self_func!(PyNumberIModProtocol, T::__imod__); + TypedSlot( + ffi::Py_nb_inplace_remainder, + py_binary_self_func!(PyNumberIModProtocol, Self::__imod__), + ) } - pub fn set_ipow(&mut self) + + fn get_ipow() -> TypedSlot where - T: for<'p> PyNumberIPowProtocol<'p>, + Self: for<'p> PyNumberIPowProtocol<'p>, { // NOTE: Somehow __ipow__ causes SIGSEGV in Python < 3.8 when we extract, // so we ignore it. It's the same as what CPython does. unsafe extern "C" fn wrap_ipow( - slf: *mut crate::ffi::PyObject, - other: *mut crate::ffi::PyObject, - _modulo: *mut crate::ffi::PyObject, - ) -> *mut crate::ffi::PyObject + slf: *mut ffi::PyObject, + other: *mut ffi::PyObject, + _modulo: *mut ffi::PyObject, + ) -> *mut ffi::PyObject where T: for<'p> PyNumberIPowProtocol<'p>, { @@ -945,132 +1099,201 @@ impl ffi::PyNumberMethods { Ok::<_, PyErr>(slf) }) } - self.nb_inplace_power = Some(wrap_ipow::); + + TypedSlot(ffi::Py_nb_inplace_power, wrap_ipow::) } - pub fn set_ilshift(&mut self) + + fn get_ilshift() -> TypedSlot where - T: for<'p> PyNumberILShiftProtocol<'p>, + Self: for<'p> PyNumberILShiftProtocol<'p>, { - self.nb_inplace_lshift = py_binary_self_func!(PyNumberILShiftProtocol, T::__ilshift__); + TypedSlot( + ffi::Py_nb_inplace_lshift, + py_binary_self_func!(PyNumberILShiftProtocol, Self::__ilshift__), + ) } - pub fn set_irshift(&mut self) + + fn get_irshift() -> TypedSlot where - T: for<'p> PyNumberIRShiftProtocol<'p>, + Self: for<'p> PyNumberIRShiftProtocol<'p>, { - self.nb_inplace_rshift = py_binary_self_func!(PyNumberIRShiftProtocol, T::__irshift__); + TypedSlot( + ffi::Py_nb_inplace_rshift, + py_binary_self_func!(PyNumberIRShiftProtocol, Self::__irshift__), + ) } - pub fn set_iand(&mut self) + + fn get_iand() -> TypedSlot where - T: for<'p> PyNumberIAndProtocol<'p>, + Self: for<'p> PyNumberIAndProtocol<'p>, { - self.nb_inplace_and = py_binary_self_func!(PyNumberIAndProtocol, T::__iand__); + TypedSlot( + ffi::Py_nb_inplace_and, + py_binary_self_func!(PyNumberIAndProtocol, Self::__iand__), + ) } - pub fn set_ixor(&mut self) + + fn get_ixor() -> TypedSlot where - T: for<'p> PyNumberIXorProtocol<'p>, + Self: for<'p> PyNumberIXorProtocol<'p>, { - self.nb_inplace_xor = py_binary_self_func!(PyNumberIXorProtocol, T::__ixor__); + TypedSlot( + ffi::Py_nb_inplace_xor, + py_binary_self_func!(PyNumberIXorProtocol, Self::__ixor__), + ) } - pub fn set_ior(&mut self) + + fn get_ior() -> TypedSlot where - T: for<'p> PyNumberIOrProtocol<'p>, + Self: for<'p> PyNumberIOrProtocol<'p>, { - self.nb_inplace_or = py_binary_self_func!(PyNumberIOrProtocol, T::__ior__); + TypedSlot( + ffi::Py_nb_inplace_or, + py_binary_self_func!(PyNumberIOrProtocol, Self::__ior__), + ) } - pub fn set_floordiv_rfloordiv(&mut self) + + fn get_floordiv_rfloordiv() -> TypedSlot where - T: for<'p> PyNumberFloordivProtocol<'p> + for<'p> PyNumberRFloordivProtocol<'p>, + Self: for<'p> PyNumberFloordivProtocol<'p> + for<'p> PyNumberRFloordivProtocol<'p>, { - self.nb_floor_divide = py_binary_fallback_num_func!( - T, - PyNumberFloordivProtocol::__floordiv__, - PyNumberRFloordivProtocol::__rfloordiv__ - ); + TypedSlot( + ffi::Py_nb_floor_divide, + py_binary_fallback_num_func!( + Self, + PyNumberFloordivProtocol::__floordiv__, + PyNumberRFloordivProtocol::__rfloordiv__ + ), + ) } - pub fn set_floordiv(&mut self) + + fn get_floordiv() -> TypedSlot where - T: for<'p> PyNumberFloordivProtocol<'p>, + Self: for<'p> PyNumberFloordivProtocol<'p>, { - self.nb_floor_divide = py_binary_num_func!(PyNumberFloordivProtocol, T::__floordiv__); + TypedSlot( + ffi::Py_nb_floor_divide, + py_binary_num_func!(PyNumberFloordivProtocol, Self::__floordiv__), + ) } - pub fn set_rfloordiv(&mut self) + + fn get_rfloordiv() -> TypedSlot where - T: for<'p> PyNumberRFloordivProtocol<'p>, + Self: for<'p> PyNumberRFloordivProtocol<'p>, { - self.nb_floor_divide = - py_binary_reversed_num_func!(PyNumberRFloordivProtocol, T::__rfloordiv__); + TypedSlot( + ffi::Py_nb_floor_divide, + py_binary_reversed_num_func!(PyNumberRFloordivProtocol, Self::__rfloordiv__), + ) } - pub fn set_truediv_rtruediv(&mut self) + + fn get_truediv_rtruediv() -> TypedSlot where - T: for<'p> PyNumberTruedivProtocol<'p> + for<'p> PyNumberRTruedivProtocol<'p>, + Self: for<'p> PyNumberTruedivProtocol<'p> + for<'p> PyNumberRTruedivProtocol<'p>, { - self.nb_true_divide = py_binary_fallback_num_func!( - T, - PyNumberTruedivProtocol::__truediv__, - PyNumberRTruedivProtocol::__rtruediv__ - ); + TypedSlot( + ffi::Py_nb_true_divide, + py_binary_fallback_num_func!( + Self, + PyNumberTruedivProtocol::__truediv__, + PyNumberRTruedivProtocol::__rtruediv__ + ), + ) } - pub fn set_truediv(&mut self) + + fn get_truediv() -> TypedSlot where - T: for<'p> PyNumberTruedivProtocol<'p>, + Self: for<'p> PyNumberTruedivProtocol<'p>, { - self.nb_true_divide = py_binary_num_func!(PyNumberTruedivProtocol, T::__truediv__); + TypedSlot( + ffi::Py_nb_true_divide, + py_binary_num_func!(PyNumberTruedivProtocol, Self::__truediv__), + ) } - pub fn set_rtruediv(&mut self) + + fn get_rtruediv() -> TypedSlot where - T: for<'p> PyNumberRTruedivProtocol<'p>, + Self: for<'p> PyNumberRTruedivProtocol<'p>, { - self.nb_true_divide = - py_binary_reversed_num_func!(PyNumberRTruedivProtocol, T::__rtruediv__); + TypedSlot( + ffi::Py_nb_true_divide, + py_binary_reversed_num_func!(PyNumberRTruedivProtocol, Self::__rtruediv__), + ) } - pub fn set_ifloordiv(&mut self) + + fn get_ifloordiv() -> TypedSlot where - T: for<'p> PyNumberIFloordivProtocol<'p>, + Self: for<'p> PyNumberIFloordivProtocol<'p>, { - self.nb_inplace_floor_divide = - py_binary_self_func!(PyNumberIFloordivProtocol, T::__ifloordiv__); + TypedSlot( + ffi::Py_nb_inplace_floor_divide, + py_binary_self_func!(PyNumberIFloordivProtocol, Self::__ifloordiv__), + ) } - pub fn set_itruediv(&mut self) + + fn get_itruediv() -> TypedSlot where - T: for<'p> PyNumberITruedivProtocol<'p>, + Self: for<'p> PyNumberITruedivProtocol<'p>, { - self.nb_inplace_true_divide = - py_binary_self_func!(PyNumberITruedivProtocol, T::__itruediv__); + TypedSlot( + ffi::Py_nb_inplace_true_divide, + py_binary_self_func!(PyNumberITruedivProtocol, Self::__itruediv__), + ) } - pub fn set_index(&mut self) + + fn get_index() -> TypedSlot where - T: for<'p> PyNumberIndexProtocol<'p>, + Self: for<'p> PyNumberIndexProtocol<'p>, { - self.nb_index = py_unary_func!(PyNumberIndexProtocol, T::__index__); + TypedSlot( + ffi::Py_nb_index, + py_unary_func!(PyNumberIndexProtocol, Self::__index__), + ) } - pub fn set_matmul_rmatmul(&mut self) + + fn get_matmul_rmatmul() -> TypedSlot where - T: for<'p> PyNumberMatmulProtocol<'p> + for<'p> PyNumberRMatmulProtocol<'p>, + Self: for<'p> PyNumberMatmulProtocol<'p> + for<'p> PyNumberRMatmulProtocol<'p>, { - self.nb_matrix_multiply = py_binary_fallback_num_func!( - T, - PyNumberMatmulProtocol::__matmul__, - PyNumberRMatmulProtocol::__rmatmul__ - ); + TypedSlot( + ffi::Py_nb_matrix_multiply, + py_binary_fallback_num_func!( + Self, + PyNumberMatmulProtocol::__matmul__, + PyNumberRMatmulProtocol::__rmatmul__ + ), + ) } - pub fn set_matmul(&mut self) + + fn get_matmul() -> TypedSlot where - T: for<'p> PyNumberMatmulProtocol<'p>, + Self: for<'p> PyNumberMatmulProtocol<'p>, { - self.nb_matrix_multiply = py_binary_num_func!(PyNumberMatmulProtocol, T::__matmul__); + TypedSlot( + ffi::Py_nb_matrix_multiply, + py_binary_num_func!(PyNumberMatmulProtocol, Self::__matmul__), + ) } - pub fn set_rmatmul(&mut self) + + fn get_rmatmul() -> TypedSlot where - T: for<'p> PyNumberRMatmulProtocol<'p>, + Self: for<'p> PyNumberRMatmulProtocol<'p>, { - self.nb_matrix_multiply = - py_binary_reversed_num_func!(PyNumberRMatmulProtocol, T::__rmatmul__); + TypedSlot( + ffi::Py_nb_matrix_multiply, + py_binary_reversed_num_func!(PyNumberRMatmulProtocol, Self::__rmatmul__), + ) } - pub fn set_imatmul(&mut self) + + fn get_imatmul() -> TypedSlot where - T: for<'p> PyNumberIMatmulProtocol<'p>, + Self: for<'p> PyNumberIMatmulProtocol<'p>, { - self.nb_inplace_matrix_multiply = - py_binary_self_func!(PyNumberIMatmulProtocol, T::__imatmul__); + TypedSlot( + ffi::Py_nb_inplace_matrix_multiply, + py_binary_self_func!(PyNumberIMatmulProtocol, Self::__imatmul__), + ) } } + +impl<'p, T> PyNumberSlots for T where T: PyNumberProtocol<'p> {} diff --git a/src/class/proto_methods.rs b/src/class/proto_methods.rs index 76e75d9f440..eff528f0873 100644 --- a/src/class/proto_methods.rs +++ b/src/class/proto_methods.rs @@ -1,154 +1,78 @@ -use crate::class::{ - basic::PyObjectMethods, descr::PyDescrMethods, gc::PyGCMethods, iter::PyIterMethods, -}; -use crate::ffi::{ - PyAsyncMethods, PyBufferProcs, PyMappingMethods, PyNumberMethods, PySequenceMethods, -}; -use std::{ - ptr::{self, NonNull}, - sync::atomic::{AtomicPtr, Ordering}, -}; +use crate::ffi; +#[cfg(not(Py_LIMITED_API))] +use crate::ffi::PyBufferProcs; + +/// ABI3 doesn't have buffer APIs, so here we define the empty one. +#[cfg(Py_LIMITED_API)] +#[doc(hidden)] +#[derive(Clone)] +pub struct PyBufferProcs; -/// Defines all method tables we need for object protocols. // Note(kngwyu): default implementations are for rust-numpy. Please don't remove them. pub trait PyProtoMethods { - fn async_methods() -> Option> { - None - } - fn basic_methods() -> Option> { - None - } - fn buffer_methods() -> Option> { - None - } - fn descr_methods() -> Option> { - None - } - fn gc_methods() -> Option> { - None - } - fn mapping_methods() -> Option> { - None - } - fn number_methods() -> Option> { - None + fn get_type_slots() -> Vec { + vec![] } - fn iter_methods() -> Option> { - None - } - fn sequence_methods() -> Option> { + fn get_buffer() -> Option { None } } -/// Indicates that a type has a protocol registry. Implemented by `#[pyclass]`. +/// Typed version of `ffi::PyType_Slot` #[doc(hidden)] -pub trait HasProtoRegistry: Sized + 'static { - fn registry() -> &'static PyProtoRegistry; +pub struct TypedSlot(pub std::os::raw::c_int, pub T); + +#[doc(hidden)] +pub enum PyProtoMethodDef { + Slots(Vec), + Buffer(PyBufferProcs), } -impl PyProtoMethods for T { - fn async_methods() -> Option> { - NonNull::new(Self::registry().async_methods.load(Ordering::Relaxed)) - } - fn basic_methods() -> Option> { - NonNull::new(Self::registry().basic_methods.load(Ordering::Relaxed)) +impl From> for PyProtoMethodDef { + fn from(slots: Vec) -> Self { + PyProtoMethodDef::Slots(slots) } - fn buffer_methods() -> Option> { - NonNull::new(Self::registry().buffer_methods.load(Ordering::Relaxed)) - } - fn descr_methods() -> Option> { - NonNull::new(Self::registry().descr_methods.load(Ordering::Relaxed)) - } - fn gc_methods() -> Option> { - NonNull::new(Self::registry().gc_methods.load(Ordering::Relaxed)) - } - fn mapping_methods() -> Option> { - NonNull::new(Self::registry().mapping_methods.load(Ordering::Relaxed)) - } - fn number_methods() -> Option> { - NonNull::new(Self::registry().number_methods.load(Ordering::Relaxed)) - } - fn iter_methods() -> Option> { - NonNull::new(Self::registry().iter_methods.load(Ordering::Relaxed)) - } - fn sequence_methods() -> Option> { - NonNull::new(Self::registry().sequence_methods.load(Ordering::Relaxed)) +} + +impl From for PyProtoMethodDef { + fn from(buffer_procs: PyBufferProcs) -> Self { + PyProtoMethodDef::Buffer(buffer_procs) } } -/// Stores all method protocols. -/// Used in the proc-macro code as a static variable. #[doc(hidden)] -pub struct PyProtoRegistry { - /// Async protocols. - async_methods: AtomicPtr, - /// Basic protocols. - basic_methods: AtomicPtr, - /// Buffer protocols. - buffer_methods: AtomicPtr, - /// Descr pProtocols. - descr_methods: AtomicPtr, - /// GC protocols. - gc_methods: AtomicPtr, - /// Mapping protocols. - mapping_methods: AtomicPtr, - /// Number protocols. - number_methods: AtomicPtr, - /// Iterator protocols. - iter_methods: AtomicPtr, - /// Sequence protocols. - sequence_methods: AtomicPtr, +#[cfg(feature = "macros")] +pub trait PyProtoInventory: inventory::Collect { + fn new(methods: PyProtoMethodDef) -> Self; + fn get(&'static self) -> &'static PyProtoMethodDef; } -impl PyProtoRegistry { - pub const fn new() -> Self { - PyProtoRegistry { - async_methods: AtomicPtr::new(ptr::null_mut()), - basic_methods: AtomicPtr::new(ptr::null_mut()), - buffer_methods: AtomicPtr::new(ptr::null_mut()), - descr_methods: AtomicPtr::new(ptr::null_mut()), - gc_methods: AtomicPtr::new(ptr::null_mut()), - mapping_methods: AtomicPtr::new(ptr::null_mut()), - number_methods: AtomicPtr::new(ptr::null_mut()), - iter_methods: AtomicPtr::new(ptr::null_mut()), - sequence_methods: AtomicPtr::new(ptr::null_mut()), - } - } - pub fn set_async_methods(&self, methods: PyAsyncMethods) { - self.async_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_basic_methods(&self, methods: PyObjectMethods) { - self.basic_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_buffer_methods(&self, methods: PyBufferProcs) { - self.buffer_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_descr_methods(&self, methods: PyDescrMethods) { - self.descr_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_gc_methods(&self, methods: PyGCMethods) { - self.gc_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_mapping_methods(&self, methods: PyMappingMethods) { - self.mapping_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_number_methods(&self, methods: PyNumberMethods) { - self.number_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) - } - pub fn set_iter_methods(&self, methods: PyIterMethods) { - self.iter_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) +#[doc(hidden)] +#[cfg(feature = "macros")] +pub trait HasProtoInventory { + type ProtoMethods: PyProtoInventory; +} + +#[cfg(feature = "macros")] +impl PyProtoMethods for T { + fn get_type_slots() -> Vec { + inventory::iter:: + .into_iter() + .filter_map(|def| match def.get() { + PyProtoMethodDef::Slots(slots) => Some(slots), + PyProtoMethodDef::Buffer(_) => None, + }) + .flatten() + .cloned() + .collect() } - pub fn set_sequence_methods(&self, methods: PySequenceMethods) { - self.sequence_methods - .store(Box::into_raw(Box::new(methods)), Ordering::Relaxed) + + fn get_buffer() -> Option { + inventory::iter:: + .into_iter() + .find_map(|def| match def.get() { + PyProtoMethodDef::Slots(_) => None, + PyProtoMethodDef::Buffer(buf) => Some(buf.clone()), + }) } } diff --git a/src/class/pyasync.rs b/src/class/pyasync.rs index ddf30b56d3a..226656fd41a 100644 --- a/src/class/pyasync.rs +++ b/src/class/pyasync.rs @@ -8,6 +8,7 @@ //! [PEP-0492](https://www.python.org/dev/peps/pep-0492/) //! +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::derive_utils::TryFromPyCell; use crate::err::PyResult; @@ -85,28 +86,43 @@ pub trait PyAsyncAexitProtocol<'p>: PyAsyncProtocol<'p> { type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl ffi::PyAsyncMethods { - pub fn set_await(&mut self) +pub trait PyAsyncSlots { + fn get_await() -> TypedSlot where - T: for<'p> PyAsyncAwaitProtocol<'p>, + Self: for<'p> PyAsyncAwaitProtocol<'p>, { - self.am_await = py_unarys_func!(PyAsyncAwaitProtocol, T::__await__); + TypedSlot( + ffi::Py_am_await, + py_unarys_func!(PyAsyncAwaitProtocol, Self::__await__), + ) } - pub fn set_aiter(&mut self) + + fn get_aiter() -> TypedSlot where - T: for<'p> PyAsyncAiterProtocol<'p>, + Self: for<'p> PyAsyncAiterProtocol<'p>, { - self.am_aiter = py_unarys_func!(PyAsyncAiterProtocol, T::__aiter__); + TypedSlot( + ffi::Py_am_aiter, + py_unarys_func!(PyAsyncAiterProtocol, Self::__aiter__), + ) } - pub fn set_anext(&mut self) + + fn get_anext() -> TypedSlot where - T: for<'p> PyAsyncAnextProtocol<'p>, + Self: for<'p> PyAsyncAnextProtocol<'p>, { - self.am_anext = am_anext::(); + TypedSlot( + ffi::Py_am_anext, + py_unarys_func!(PyAsyncAnextProtocol, Self::__anext__), + ) } } +impl<'p, T> PyAsyncSlots for T where T: PyAsyncProtocol<'p> {} + +/// Output of `__anext__`. pub enum IterANextOutput { Yield(T), Return(U), @@ -149,11 +165,3 @@ where } } } - -#[inline] -fn am_anext() -> Option -where - T: for<'p> PyAsyncAnextProtocol<'p>, -{ - py_unarys_func!(PyAsyncAnextProtocol, T::__anext__) -} diff --git a/src/class/sequence.rs b/src/class/sequence.rs index d2f29a7f216..7ac3ffd9637 100644 --- a/src/class/sequence.rs +++ b/src/class/sequence.rs @@ -3,6 +3,7 @@ //! Python Sequence Interface //! Trait and support implementation for implementing sequence +use super::proto_methods::TypedSlot; use crate::callback::IntoPyCallbackOutput; use crate::conversion::{FromPyObject, IntoPy}; use crate::err::PyErr; @@ -128,88 +129,52 @@ pub trait PySequenceInplaceRepeatProtocol<'p>: type Result: IntoPyCallbackOutput; } +/// Extension trait for proc-macro backend. #[doc(hidden)] -impl ffi::PySequenceMethods { - pub fn set_len(&mut self) +pub trait PySequenceSlots { + fn get_len() -> TypedSlot where - T: for<'p> PySequenceLenProtocol<'p>, + Self: for<'p> PySequenceLenProtocol<'p>, { - self.sq_length = py_len_func!(PySequenceLenProtocol, T::__len__); - } - pub fn set_concat(&mut self) - where - T: for<'p> PySequenceConcatProtocol<'p>, - { - self.sq_concat = py_binary_func!(PySequenceConcatProtocol, T::__concat__); - } - pub fn set_repeat(&mut self) - where - T: for<'p> PySequenceRepeatProtocol<'p>, - { - self.sq_repeat = py_ssizearg_func!(PySequenceRepeatProtocol, T::__repeat__); - } - pub fn set_getitem(&mut self) - where - T: for<'p> PySequenceGetItemProtocol<'p>, - { - self.sq_item = py_ssizearg_func!(PySequenceGetItemProtocol, T::__getitem__); - } - pub fn set_setitem(&mut self) - where - T: for<'p> PySequenceSetItemProtocol<'p>, - { - self.sq_ass_item = sq_ass_item_impl::set_item::(); - } - pub fn set_delitem(&mut self) - where - T: for<'p> PySequenceDelItemProtocol<'p>, - { - self.sq_ass_item = sq_ass_item_impl::del_item::(); - } - pub fn set_setdelitem(&mut self) - where - T: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>, - { - self.sq_ass_item = sq_ass_item_impl::set_del_item::(); + TypedSlot( + ffi::Py_sq_length, + py_len_func!(PySequenceLenProtocol, Self::__len__), + ) } - pub fn set_contains(&mut self) + + fn get_concat() -> TypedSlot where - T: for<'p> PySequenceContainsProtocol<'p>, + Self: for<'p> PySequenceConcatProtocol<'p>, { - self.sq_contains = py_binary_func!(PySequenceContainsProtocol, T::__contains__, c_int); + TypedSlot( + ffi::Py_sq_concat, + py_binary_func!(PySequenceConcatProtocol, Self::__concat__), + ) } - pub fn set_inplace_concat(&mut self) + + fn get_repeat() -> TypedSlot where - T: for<'p> PySequenceInplaceConcatProtocol<'p>, + Self: for<'p> PySequenceRepeatProtocol<'p>, { - self.sq_inplace_concat = py_binary_func!( - PySequenceInplaceConcatProtocol, - T::__inplace_concat__, - *mut ffi::PyObject, - call_mut + TypedSlot( + ffi::Py_sq_repeat, + py_ssizearg_func!(PySequenceRepeatProtocol, Self::__repeat__), ) } - pub fn set_inplace_repeat(&mut self) + + fn get_getitem() -> TypedSlot where - T: for<'p> PySequenceInplaceRepeatProtocol<'p>, + Self: for<'p> PySequenceGetItemProtocol<'p>, { - self.sq_inplace_repeat = py_ssizearg_func!( - PySequenceInplaceRepeatProtocol, - T::__inplace_repeat__, - call_mut + TypedSlot( + ffi::Py_sq_item, + py_ssizearg_func!(PySequenceGetItemProtocol, Self::__getitem__), ) } -} - -/// It can be possible to delete and set items (PySequenceSetItemProtocol and -/// PySequenceDelItemProtocol implemented), only to delete (PySequenceDelItemProtocol implemented) -/// or no deleting or setting is possible -mod sq_ass_item_impl { - use super::*; - pub(super) fn set_item() -> Option + fn get_setitem() -> TypedSlot where - T: for<'p> PySequenceSetItemProtocol<'p>, + Self: for<'p> PySequenceSetItemProtocol<'p>, { unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, @@ -235,12 +200,13 @@ mod sq_ass_item_impl { crate::callback::convert(py, slf.__setitem__(key.into(), value)) }) } - Some(wrap::) + + TypedSlot(ffi::Py_sq_ass_item, wrap::) } - pub(super) fn del_item() -> Option + fn get_delitem() -> TypedSlot where - T: for<'p> PySequenceDelItemProtocol<'p>, + Self: for<'p> PySequenceDelItemProtocol<'p>, { unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, @@ -263,12 +229,13 @@ mod sq_ass_item_impl { } }) } - Some(wrap::) + + TypedSlot(ffi::Py_sq_ass_item, wrap::) } - pub(super) fn set_del_item() -> Option + fn get_setdelitem() -> TypedSlot where - T: for<'p> PySequenceSetItemProtocol<'p> + for<'p> PySequenceDelItemProtocol<'p>, + Self: for<'p> PySequenceDelItemProtocol<'p> + for<'p> PySequenceSetItemProtocol<'p>, { unsafe extern "C" fn wrap( slf: *mut ffi::PyObject, @@ -291,6 +258,48 @@ mod sq_ass_item_impl { } }) } - Some(wrap::) + + TypedSlot(ffi::Py_sq_ass_item, wrap::) + } + + fn get_contains() -> TypedSlot + where + Self: for<'p> PySequenceContainsProtocol<'p>, + { + TypedSlot( + ffi::Py_sq_contains, + py_binary_func!(PySequenceContainsProtocol, Self::__contains__, c_int), + ) + } + + fn get_inplace_concat() -> TypedSlot + where + Self: for<'p> PySequenceInplaceConcatProtocol<'p>, + { + TypedSlot( + ffi::Py_sq_inplace_concat, + py_binary_func!( + PySequenceInplaceConcatProtocol, + Self::__inplace_concat__, + *mut ffi::PyObject, + call_mut + ), + ) + } + + fn get_inplace_repeat() -> TypedSlot + where + Self: for<'p> PySequenceInplaceRepeatProtocol<'p>, + { + TypedSlot( + ffi::Py_sq_inplace_repeat, + py_ssizearg_func!( + PySequenceInplaceRepeatProtocol, + Self::__inplace_repeat__, + call_mut + ), + ) } } + +impl<'p, T> PySequenceSlots for T where T: PySequenceProtocol<'p> {} diff --git a/src/err/mod.rs b/src/err/mod.rs index 461a1ad49ad..8a1b93987f8 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -491,7 +491,7 @@ impl<'a> std::fmt::Display for PyDowncastError<'a> { write!( f, "'{}' object cannot be converted to '{}'", - self.from.get_type().name(), + self.from.get_type().name().map_err(|_| std::fmt::Error)?, self.to ) } diff --git a/src/exceptions.rs b/src/exceptions.rs index 97483d41ce8..161501f36a9 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -29,8 +29,8 @@ macro_rules! impl_exception_boilerplate { impl std::fmt::Debug for $name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let type_name = self.get_type().name(); - f.debug_struct(&*type_name) + let type_name = self.get_type().name().map_err(|_| std::fmt::Error)?; + f.debug_struct(type_name) // TODO: print out actual fields! .finish() } @@ -38,7 +38,7 @@ macro_rules! impl_exception_boilerplate { impl std::fmt::Display for $name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let type_name = self.get_type().name(); + let type_name = self.get_type().name().map_err(|_| std::fmt::Error)?; write!(f, "{}", type_name)?; if let Ok(s) = self.str() { write!(f, ": {}", &s.to_string_lossy()) diff --git a/src/ffi/ceval.rs b/src/ffi/ceval.rs index 5df1d8c28ee..7ac31c7151e 100644 --- a/src/ffi/ceval.rs +++ b/src/ffi/ceval.rs @@ -1,3 +1,4 @@ +#[cfg(not(Py_LIMITED_API))] use crate::ffi::code::FreeFunc; use crate::ffi::object::PyObject; use crate::ffi::pystate::{PyThreadState, Py_tracefunc}; @@ -61,6 +62,7 @@ extern "C" { arg1: *mut crate::ffi::PyFrameObject, exc: c_int, ) -> *mut PyObject; + #[cfg(not(Py_LIMITED_API))] pub fn _PyEval_RequestCodeExtraIndex(func: FreeFunc) -> c_int; pub fn PyEval_EvalFrameEx(f: *mut crate::ffi::PyFrameObject, exc: c_int) -> *mut PyObject; #[cfg_attr(PyPy, link_name = "PyPyEval_SaveThread")] diff --git a/src/ffi/descrobject.rs b/src/ffi/descrobject.rs index 828b85c941d..6cdb91a4626 100644 --- a/src/ffi/descrobject.rs +++ b/src/ffi/descrobject.rs @@ -1,13 +1,12 @@ use crate::ffi::methodobject::PyMethodDef; use crate::ffi::object::{PyObject, PyTypeObject}; -#[cfg(not(PyPy))] +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] use crate::ffi::object::{PyObject_GenericGetDict, PyObject_GenericSetDict}; use crate::ffi::structmember::PyMemberDef; use std::os::raw::{c_char, c_int, c_void}; use std::ptr; pub type getter = unsafe extern "C" fn(slf: *mut PyObject, closure: *mut c_void) -> *mut PyObject; - pub type setter = unsafe extern "C" fn(slf: *mut PyObject, value: *mut PyObject, closure: *mut c_void) -> c_int; @@ -29,11 +28,12 @@ pub const PyGetSetDef_INIT: PyGetSetDef = PyGetSetDef { closure: ptr::null_mut(), }; -#[cfg(PyPy)] +#[cfg(any(PyPy, Py_LIMITED_API))] pub const PyGetSetDef_DICT: PyGetSetDef = PyGetSetDef_INIT; // PyPy doesn't export neither PyObject_GenericGetDict/PyObject_GenericSetDict -#[cfg(not(PyPy))] +// Py_LIMITED_API exposes PyObject_GenericSetDict but not Get. +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] pub const PyGetSetDef_DICT: PyGetSetDef = PyGetSetDef { name: "__dict__\0".as_ptr() as *mut c_char, get: Some(PyObject_GenericGetDict), diff --git a/src/ffi/funcobject.rs b/src/ffi/funcobject.rs index 10b15345870..e57a87a7fe1 100644 --- a/src/ffi/funcobject.rs +++ b/src/ffi/funcobject.rs @@ -4,10 +4,12 @@ use crate::ffi::object::{PyObject, PyTypeObject, Py_TYPE}; #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { + #[cfg(not(Py_LIMITED_API))] #[cfg_attr(PyPy, link_name = "PyPyFunction_Type")] pub static mut PyFunction_Type: PyTypeObject; } +#[cfg(not(Py_LIMITED_API))] #[inline] pub unsafe fn PyFunction_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &mut PyFunction_Type) as c_int diff --git a/src/ffi/marshal.rs b/src/ffi/marshal.rs index 00237908832..01494ce6e51 100644 --- a/src/ffi/marshal.rs +++ b/src/ffi/marshal.rs @@ -1,6 +1,7 @@ use super::PyObject; use std::os::raw::{c_char, c_int}; +#[cfg(not(Py_LIMITED_API))] extern "C" { #[cfg_attr(PyPy, link_name = "PyPyMarshal_WriteObjectToString")] pub fn PyMarshal_WriteObjectToString(object: *mut PyObject, version: c_int) -> *mut PyObject; diff --git a/src/ffi/object.rs b/src/ffi/object.rs index d4f1b8d8676..10255639bf8 100644 --- a/src/ffi/object.rs +++ b/src/ffi/object.rs @@ -314,69 +314,14 @@ mod typeobject { pub nb_inplace_matrix_multiply: Option, } - impl Default for PyNumberMethods { - #[inline] - fn default() -> Self { - PyNumberMethods_INIT - } - } macro_rules! as_expr { ($e:expr) => { $e }; } - macro_rules! py_number_methods_init { - ($($tail:tt)*) => { - as_expr! { - PyNumberMethods { - nb_add: None, - nb_subtract: None, - nb_multiply: None, - nb_remainder: None, - nb_divmod: None, - nb_power: None, - nb_negative: None, - nb_positive: None, - nb_absolute: None, - nb_bool: None, - nb_invert: None, - nb_lshift: None, - nb_rshift: None, - nb_and: None, - nb_xor: None, - nb_or: None, - nb_int: None, - nb_reserved: ::std::ptr::null_mut(), - nb_float: None, - nb_inplace_add: None, - nb_inplace_subtract: None, - nb_inplace_multiply: None, - nb_inplace_remainder: None, - nb_inplace_power: None, - nb_inplace_lshift: None, - nb_inplace_rshift: None, - nb_inplace_and: None, - nb_inplace_xor: None, - nb_inplace_or: None, - nb_floor_divide: None, - nb_true_divide: None, - nb_inplace_floor_divide: None, - nb_inplace_true_divide: None, - nb_index: None, - $($tail)* - } - } - } - } - - pub const PyNumberMethods_INIT: PyNumberMethods = py_number_methods_init! { - nb_matrix_multiply: None, - nb_inplace_matrix_multiply: None, - }; - #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone)] pub struct PySequenceMethods { pub sq_length: Option, pub sq_concat: Option, @@ -390,83 +335,29 @@ mod typeobject { pub sq_inplace_repeat: Option, } - impl Default for PySequenceMethods { - #[inline] - fn default() -> Self { - unsafe { mem::zeroed() } - } - } - pub const PySequenceMethods_INIT: PySequenceMethods = PySequenceMethods { - sq_length: None, - sq_concat: None, - sq_repeat: None, - sq_item: None, - was_sq_slice: ptr::null_mut(), - sq_ass_item: None, - was_sq_ass_slice: ptr::null_mut(), - sq_contains: None, - sq_inplace_concat: None, - sq_inplace_repeat: None, - }; #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone, Default)] pub struct PyMappingMethods { pub mp_length: Option, pub mp_subscript: Option, pub mp_ass_subscript: Option, } - impl Default for PyMappingMethods { - #[inline] - fn default() -> Self { - unsafe { mem::zeroed() } - } - } - pub const PyMappingMethods_INIT: PyMappingMethods = PyMappingMethods { - mp_length: None, - mp_subscript: None, - mp_ass_subscript: None, - }; #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone, Default)] pub struct PyAsyncMethods { pub am_await: Option, pub am_aiter: Option, pub am_anext: Option, } - impl Default for PyAsyncMethods { - #[inline] - fn default() -> Self { - PyAsyncMethods_INIT - } - } - - pub const PyAsyncMethods_INIT: PyAsyncMethods = PyAsyncMethods { - am_await: None, - am_aiter: None, - am_anext: None, - }; - #[repr(C)] - #[derive(Copy, Clone, Debug)] + #[derive(Clone, Default)] pub struct PyBufferProcs { pub bf_getbuffer: Option, pub bf_releasebuffer: Option, } - impl Default for PyBufferProcs { - #[inline] - fn default() -> Self { - PyBufferProcs_INIT - } - } - - pub const PyBufferProcs_INIT: PyBufferProcs = PyBufferProcs { - bf_getbuffer: None, - bf_releasebuffer: None, - }; - #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct PyTypeObject { @@ -650,7 +541,7 @@ mod typeobject { pub const PyTypeObject_INIT: PyTypeObject = type_object_init!(); #[repr(C)] - #[derive(Copy, Clone)] + #[derive(Clone)] pub struct PyHeapTypeObject { pub ht_type: PyTypeObject, pub as_async: PyAsyncMethods, @@ -825,6 +716,7 @@ extern "C" { arg2: *mut PyObject, arg3: *mut PyObject, ) -> c_int; + #[cfg(not(Py_LIMITED_API))] pub fn PyObject_GenericGetDict(arg1: *mut PyObject, arg2: *mut c_void) -> *mut PyObject; pub fn PyObject_GenericSetDict( arg1: *mut PyObject, diff --git a/src/ffi/objectabstract.rs b/src/ffi/objectabstract.rs index 76154c33dea..bead5ebf326 100644 --- a/src/ffi/objectabstract.rs +++ b/src/ffi/objectabstract.rs @@ -143,6 +143,11 @@ pub unsafe fn PyIter_Check(o: *mut PyObject) -> c_int { }) as c_int } +#[cfg(all(Py_LIMITED_API, Py_3_8))] +extern "C" { + pub fn PyIter_Check(obj: *mut PyObject) -> c_int; +} + extern "C" { #[cfg_attr(PyPy, link_name = "PyPyIter_Next")] pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject; diff --git a/src/freelist.rs b/src/freelist.rs index 5b56c337ff1..98a18cb51eb 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -2,7 +2,7 @@ //! Free allocation list -use crate::pyclass::{tp_free_fallback, PyClassAlloc}; +use crate::pyclass::{get_type_free, tp_free_fallback, PyClassAlloc}; use crate::type_object::{PyLayout, PyTypeInfo}; use crate::{ffi, AsPyPointer, FromPyPointer, PyAny, Python}; use std::mem; @@ -85,15 +85,14 @@ where unsafe fn dealloc(py: Python, self_: *mut Self::Layout) { (*self_).py_drop(py); let obj = PyAny::from_borrowed_ptr_or_panic(py, self_ as _); - if Self::is_exact_instance(obj) && ffi::PyObject_CallFinalizerFromDealloc(obj.as_ptr()) < 0 - { - // tp_finalize resurrected. - return; - } if let Some(obj) = ::get_free_list(py).insert(obj.as_ptr()) { - match (*ffi::Py_TYPE(obj)).tp_free { - Some(free) => free(obj as *mut c_void), + match get_type_free(ffi::Py_TYPE(obj)) { + Some(free) => { + let ty = ffi::Py_TYPE(obj); + free(obj as *mut c_void); + ffi::Py_DECREF(ty as *mut ffi::PyObject); + } None => tp_free_fallback(obj), } } diff --git a/src/lib.rs b/src/lib.rs index bc14deaba26..67544e4f095 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,6 +184,7 @@ mod gil; mod instance; #[macro_use] mod internal_tricks; +#[cfg(not(Py_LIMITED_API))] pub mod marshal; pub mod once_cell; pub mod panic; diff --git a/src/marshal.rs b/src/marshal.rs index d0c7350b912..342274189c8 100644 --- a/src/marshal.rs +++ b/src/marshal.rs @@ -1,3 +1,7 @@ +#![cfg(not(Py_LIMITED_API))] +//! Support for the Python `marshal` format. Not supported in limited API +//! builds. + use crate::ffi; use crate::types::{PyAny, PyBytes}; use crate::{AsPyPointer, FromPyPointer, PyResult, Python}; diff --git a/src/pycell.rs b/src/pycell.rs index d0d498d2d8e..da6a73f998d 100644 --- a/src/pycell.rs +++ b/src/pycell.rs @@ -8,9 +8,9 @@ use crate::type_object::{PyBorrowFlagLayout, PyLayout, PySizedLayout, PyTypeInfo use crate::types::PyAny; use crate::{ffi, IntoPy, PyErr, PyNativeType, PyObject, PyResult, Python}; use std::cell::{Cell, UnsafeCell}; +use std::fmt; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; -use std::{fmt, mem}; /// Base layout of PyCell. /// This is necessary for sharing BorrowFlag between parents and children. @@ -170,20 +170,26 @@ pub struct PyCell { impl PyCell { /// Get the offset of the dictionary from the start of the struct in bytes. + #[cfg(not(Py_LIMITED_API))] pub(crate) fn dict_offset() -> Option { if T::Dict::IS_DUMMY { None } else { - Some(mem::size_of::() - mem::size_of::() - mem::size_of::()) + Some( + std::mem::size_of::() + - std::mem::size_of::() + - std::mem::size_of::(), + ) } } /// Get the offset of the weakref list from the start of the struct in bytes. + #[cfg(not(Py_LIMITED_API))] pub(crate) fn weakref_offset() -> Option { if T::WeakRef::IS_DUMMY { None } else { - Some(mem::size_of::() - mem::size_of::()) + Some(std::mem::size_of::() - std::mem::size_of::()) } } } @@ -455,7 +461,7 @@ impl fmt::Debug for PyCell { /// - You want to get super class. /// ``` /// # use pyo3::prelude::*; -/// #[pyclass] +/// #[pyclass(subclass)] /// struct Parent { /// basename: &'static str, /// } @@ -516,11 +522,11 @@ where /// # Examples /// ``` /// # use pyo3::prelude::*; - /// #[pyclass] + /// #[pyclass(subclass)] /// struct Base1 { /// name1: &'static str, /// } - /// #[pyclass(extends=Base1)] + /// #[pyclass(extends=Base1, subclass)] /// struct Base2 { /// name2: &'static str, /// } diff --git a/src/pyclass.rs b/src/pyclass.rs index 11e4060d64a..739e1528d13 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -7,11 +7,34 @@ use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{type_flags, PyLayout}; use crate::types::PyAny; use crate::{ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python}; +use std::convert::TryInto; use std::ffi::CString; use std::marker::PhantomData; -use std::os::raw::c_void; +#[cfg(not(PyPy))] +use std::mem; +use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::{ptr, thread}; +#[cfg(PyPy)] +unsafe fn get_type_alloc(tp: *mut ffi::PyTypeObject) -> Option { + (*tp).tp_alloc +} + +#[cfg(not(PyPy))] +unsafe fn get_type_alloc(tp: *mut ffi::PyTypeObject) -> Option { + mem::transmute(ffi::PyType_GetSlot(tp, ffi::Py_tp_alloc)) +} + +#[cfg(PyPy)] +pub(crate) unsafe fn get_type_free(tp: *mut ffi::PyTypeObject) -> Option { + (*tp).tp_free +} + +#[cfg(not(PyPy))] +pub(crate) unsafe fn get_type_free(tp: *mut ffi::PyTypeObject) -> Option { + mem::transmute(ffi::PyType_GetSlot(tp, ffi::Py_tp_free)) +} + #[inline] pub(crate) unsafe fn default_new( py: Python, @@ -19,12 +42,21 @@ pub(crate) unsafe fn default_new( ) -> *mut ffi::PyObject { // if the class derives native types(e.g., PyDict), call special new if T::FLAGS & type_flags::EXTENDED != 0 && T::BaseLayout::IS_NATIVE_TYPE { - let base_tp = T::BaseType::type_object_raw(py); - if let Some(base_new) = (*base_tp).tp_new { - return base_new(subtype, ptr::null_mut(), ptr::null_mut()); + #[cfg(not(Py_LIMITED_API))] + { + let base_tp = T::BaseType::type_object_raw(py); + if let Some(base_new) = (*base_tp).tp_new { + return base_new(subtype, ptr::null_mut(), ptr::null_mut()); + } + } + #[cfg(Py_LIMITED_API)] + { + // Silence unused parameter warning. + let _ = py; + unreachable!("Subclassing native types isn't support in limited API mode"); } } - let alloc = (*subtype).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); + let alloc = get_type_alloc(subtype).unwrap_or(ffi::PyType_GenericAlloc); alloc(subtype, 0) as _ } @@ -45,14 +77,13 @@ pub trait PyClassAlloc: PyTypeInfo + Sized { unsafe fn dealloc(py: Python, self_: *mut Self::Layout) { (*self_).py_drop(py); let obj = PyAny::from_borrowed_ptr_or_panic(py, self_ as _); - if Self::is_exact_instance(obj) && ffi::PyObject_CallFinalizerFromDealloc(obj.as_ptr()) < 0 - { - // tp_finalize resurrected. - return; - } - match (*ffi::Py_TYPE(obj.as_ptr())).tp_free { - Some(free) => free(obj.as_ptr() as *mut c_void), + match get_type_free(ffi::Py_TYPE(obj.as_ptr())) { + Some(free) => { + let ty = ffi::Py_TYPE(obj.as_ptr()); + free(obj.as_ptr() as *mut c_void); + ffi::Py_DECREF(ty as *mut ffi::PyObject); + } None => tp_free_fallback(obj.as_ptr()), } } @@ -107,134 +138,151 @@ pub trait PyClass: type BaseNativeType: PyTypeInfo + PyNativeType; } -#[cfg(not(Py_LIMITED_API))] -pub(crate) fn initialize_type_object( - py: Python, - module_name: Option<&str>, - type_object: &mut ffi::PyTypeObject, -) -> PyResult<()> -where - T: PyClass, -{ - type_object.tp_doc = match T::DESCRIPTION { - // PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though. - "\0" => ptr::null(), - s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _, - // If the description is not null-terminated, create CString and leak it - s => CString::new(s)?.into_raw(), - }; +/// For collecting slot items. +#[derive(Default)] +pub(crate) struct TypeSlots(Vec); - type_object.tp_base = T::BaseType::type_object_raw(py); +impl TypeSlots { + fn push(&mut self, slot: c_int, pfunc: *mut c_void) { + self.0.push(ffi::PyType_Slot { slot, pfunc }); + } + pub(crate) fn maybe_push(&mut self, slot: c_int, value: Option<*mut c_void>) { + if let Some(pfunc) = value { + self.push(slot, pfunc); + } + } +} + +fn tp_doc() -> PyResult> { + Ok(match T::DESCRIPTION { + "\0" => None, + s if s.as_bytes().ends_with(b"\0") => Some(s.as_ptr() as _), + // If the description is not null-terminated, create CString and leak it + s => Some(CString::new(s)?.into_raw() as _), + }) +} - type_object.tp_name = match module_name { +fn get_type_name(module_name: Option<&str>) -> PyResult<*mut c_char> { + Ok(match module_name { Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(), None => CString::new(T::NAME)?.into_raw(), - }; - - // dealloc - type_object.tp_dealloc = tp_dealloc::(); + }) +} - // type size - type_object.tp_basicsize = std::mem::size_of::() as ffi::Py_ssize_t; +fn into_raw(vec: Vec) -> *mut c_void { + Box::into_raw(vec.into_boxed_slice()) as _ +} - // __dict__ support - if let Some(dict_offset) = PyCell::::dict_offset() { - type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t; - } +pub(crate) fn create_type_object( + py: Python, + module_name: Option<&str>, +) -> PyResult<*mut ffi::PyTypeObject> +where + T: PyClass, +{ + let mut slots = TypeSlots::default(); - // weakref support - if let Some(weakref_offset) = PyCell::::weakref_offset() { - type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t; - } + slots.push(ffi::Py_tp_base, T::BaseType::type_object_raw(py) as _); + slots.maybe_push(ffi::Py_tp_doc, tp_doc::()?); + slots.maybe_push(ffi::Py_tp_dealloc, tp_dealloc::().map(|v| v as _)); - // GC support - if let Some(gc) = T::gc_methods() { - unsafe { gc.as_ref() }.update_typeobj(type_object); - } + let (new, call, methods) = py_class_method_defs::(); + slots.maybe_push(ffi::Py_tp_new, new.map(|v| v as _)); + slots.maybe_push(ffi::Py_tp_call, call.map(|v| v as _)); - // descriptor protocol - if let Some(descr) = T::descr_methods() { - unsafe { descr.as_ref() }.update_typeobj(type_object); + // normal methods + if !methods.is_empty() { + slots.push(ffi::Py_tp_methods, into_raw(methods)); } - // iterator methods - if let Some(iter) = T::iter_methods() { - unsafe { iter.as_ref() }.update_typeobj(type_object); + // properties + let props = py_class_properties::(); + if !props.is_empty() { + slots.push(ffi::Py_tp_getset, into_raw(props)); } - // nb_bool is a part of PyObjectProtocol, but should be placed under tp_as_number - let mut nb_bool = None; - // basic methods - if let Some(basic) = T::basic_methods() { - unsafe { basic.as_ref() }.update_typeobj(type_object); - nb_bool = unsafe { basic.as_ref() }.nb_bool; + // protocol methods + let mut has_gc_methods = false; + for slot in T::get_type_slots() { + has_gc_methods |= slot.slot == ffi::Py_tp_clear; + has_gc_methods |= slot.slot == ffi::Py_tp_traverse; + slots.0.push(slot); } - // number methods - type_object.tp_as_number = T::number_methods() - .map(|mut p| { - unsafe { p.as_mut() }.nb_bool = nb_bool; - p.as_ptr() - }) - .unwrap_or_else(|| nb_bool.map_or_else(ptr::null_mut, ffi::PyNumberMethods::from_nb_bool)); - // mapping methods - type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // sequence methods - type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // async methods - type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - // buffer protocol - type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr()); - - let (new, call, mut methods) = py_class_method_defs::(); + slots.push(0, ptr::null_mut()); + let mut spec = ffi::PyType_Spec { + name: get_type_name::(module_name)?, + basicsize: std::mem::size_of::() as c_int, + itemsize: 0, + flags: py_class_flags::(has_gc_methods), + slots: slots.0.as_mut_slice().as_mut_ptr(), + }; - // normal methods - if !methods.is_empty() { - methods.push(ffi::PyMethodDef_INIT); - type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _; + let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) }; + if type_object.is_null() { + Err(PyErr::fetch(py)) + } else { + tp_init_additional::(type_object as _); + Ok(type_object as _) } +} - // __new__ method - type_object.tp_new = new; - // __call__ method - type_object.tp_call = call; +#[cfg(not(Py_LIMITED_API))] +fn tp_init_additional(type_object: *mut ffi::PyTypeObject) { + // Just patch the type objects for the things there's no + // PyType_FromSpec API for... there's no reason this should work, + // except for that it does and we have tests. - // properties - let mut props = py_class_properties::(); + // Running this causes PyPy to segfault. + #[cfg(all(not(PyPy), not(Py_3_10)))] + { + if T::DESCRIPTION != "\0" { + unsafe { + // Until CPython 3.10, tp_doc was treated specially for + // heap-types, and it removed the text_signature value from it. + // We go in after the fact and replace tp_doc with something + // that _does_ include the text_signature value! + ffi::PyObject_Free((*type_object).tp_doc as _); + let data = ffi::PyObject_Malloc(T::DESCRIPTION.len()); + data.copy_from(T::DESCRIPTION.as_ptr() as _, T::DESCRIPTION.len()); + (*type_object).tp_doc = data as _; + } + } + } - if !T::Dict::IS_DUMMY { - props.push(ffi::PyGetSetDef_DICT); + if let Some(buffer) = T::get_buffer() { + unsafe { + (*(*type_object).tp_as_buffer).bf_getbuffer = buffer.bf_getbuffer; + (*(*type_object).tp_as_buffer).bf_releasebuffer = buffer.bf_releasebuffer; + } } - if !props.is_empty() { - props.push(ffi::PyGetSetDef_INIT); - type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _; + // __dict__ support + if let Some(dict_offset) = PyCell::::dict_offset() { + unsafe { + (*type_object).tp_dictoffset = dict_offset as ffi::Py_ssize_t; + } } - - // set type flags - py_class_flags::(type_object); - - // register type object - unsafe { - if ffi::PyType_Ready(type_object) == 0 { - Ok(()) - } else { - Err(PyErr::fetch(py)) + // weakref support + if let Some(weakref_offset) = PyCell::::weakref_offset() { + unsafe { + (*type_object).tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t; } } } -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; +#[cfg(Py_LIMITED_API)] +fn tp_init_additional(_type_object: *mut ffi::PyTypeObject) {} + +fn py_class_flags(has_gc_methods: bool) -> c_uint { + let mut flags = if has_gc_methods || T::FLAGS & type_flags::GC != 0 { + ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC } else { - type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; - } + ffi::Py_TPFLAGS_DEFAULT + }; if T::FLAGS & type_flags::BASETYPE != 0 { - type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE; + flags |= ffi::Py_TPFLAGS_BASETYPE; } + flags.try_into().unwrap() } pub(crate) fn py_class_attributes() -> impl Iterator { @@ -244,6 +292,21 @@ pub(crate) fn py_class_attributes() -> impl Iterator Option { + unsafe extern "C" fn fallback_new( + _subtype: *mut ffi::PyTypeObject, + _args: *mut ffi::PyObject, + _kwds: *mut ffi::PyObject, + ) -> *mut ffi::PyObject { + crate::callback_body!(py, { + Err::<(), _>(crate::exceptions::PyTypeError::new_err( + "No constructor defined", + )) + }) + } + Some(fallback_new) +} + fn py_class_method_defs() -> ( Option, Option, @@ -251,7 +314,7 @@ fn py_class_method_defs() -> ( ) { let mut defs = Vec::new(); let mut call = None; - let mut new = None; + let mut new = fallback_new(); for def in T::py_methods() { match *def { @@ -272,10 +335,14 @@ fn py_class_method_defs() -> ( } } + if !defs.is_empty() { + defs.push(ffi::PyMethodDef_INIT); + } + (new, call, defs) } -fn py_class_properties() -> Vec { +fn py_class_properties() -> Vec { let mut defs = std::collections::HashMap::new(); for def in T::py_methods() { @@ -298,7 +365,14 @@ fn py_class_properties() -> Vec { } } - defs.values().cloned().collect() + let mut props: Vec<_> = defs.values().cloned().collect(); + if !T::Dict::IS_DUMMY { + props.push(ffi::PyGetSetDef_DICT); + } + if !props.is_empty() { + props.push(ffi::PyGetSetDef_INIT); + } + props } /// This trait is implemented for `#[pyclass]` and handles following two situations: diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 81a68f2cbd1..1d73f960cfc 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -30,12 +30,12 @@ impl PyObjectInit for PyNativeTypeInitializer { /// ``` /// # use pyo3::prelude::*; /// # use pyo3::py_run; -/// #[pyclass] +/// #[pyclass(subclass)] /// struct BaseClass { /// #[pyo3(get)] /// basename: &'static str, /// } -/// #[pyclass(extends=BaseClass)] +/// #[pyclass(extends=BaseClass, subclass)] /// struct SubClass { /// #[pyo3(get)] /// subname: &'static str, diff --git a/src/type_object.rs b/src/type_object.rs index 0a4a787bf5d..35d039bfd4e 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -3,7 +3,7 @@ use crate::conversion::IntoPyPointer; use crate::once_cell::GILOnceCell; -use crate::pyclass::{initialize_type_object, py_class_attributes, PyClass}; +use crate::pyclass::{create_type_object, py_class_attributes, PyClass}; use crate::pyclass_init::PyObjectInit; use crate::types::{PyAny, PyType}; use crate::{ffi, AsPyPointer, PyErr, PyNativeType, PyObject, PyResult, Python}; @@ -157,12 +157,10 @@ impl LazyStaticType { pub fn get_or_init(&self, py: Python) -> *mut ffi::PyTypeObject { let type_object = *self.value.get_or_init(py, || { - let mut type_object = Box::new(ffi::PyTypeObject_INIT); - initialize_type_object::(py, T::MODULE, type_object.as_mut()).unwrap_or_else(|e| { + create_type_object::(py, T::MODULE).unwrap_or_else(|e| { e.print(py); panic!("An error occurred while initializing class {}", T::NAME) - }); - Box::into_raw(type_object) + }) }); // We might want to fill the `tp_dict` with python instances of `T` @@ -204,10 +202,7 @@ impl LazyStaticType { // Now we hold the GIL and we can assume it won't be released until we // return from the function. let result = self.tp_dict_filled.get_or_init(py, move || { - let tp_dict = unsafe { (*type_object).tp_dict }; - let result = initialize_tp_dict(py, tp_dict, items); - // See discussion on #982 for why we need this. - unsafe { ffi::PyType_Modified(type_object) }; + let result = initialize_tp_dict(py, type_object as *mut ffi::PyObject, items); // Initialization successfully complete, can clear the thread list. // (No further calls to get_or_init() will try to init, on any thread.) @@ -226,13 +221,13 @@ impl LazyStaticType { fn initialize_tp_dict( py: Python, - tp_dict: *mut ffi::PyObject, + type_object: *mut ffi::PyObject, items: Vec<(&'static std::ffi::CStr, PyObject)>, ) -> PyResult<()> { // We hold the GIL: the dictionary update can be considered atomic from // the POV of other threads. for (key, val) in items { - let ret = unsafe { ffi::PyDict_SetItemString(tp_dict, key.as_ptr(), val.into_ptr()) }; + let ret = unsafe { ffi::PyObject_SetAttrString(type_object, key.as_ptr(), val.into_ptr()) }; if ret < 0 { return Err(PyErr::fetch(py)); } diff --git a/src/types/complex.rs b/src/types/complex.rs index c3a38bb467c..69d92c17c72 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -1,7 +1,7 @@ -#[cfg(not(PyPy))] +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] use crate::instance::PyNativeType; use crate::{ffi, AsPyPointer, PyAny, Python}; -#[cfg(not(PyPy))] +#[cfg(all(not(PyPy), not(Py_LIMITED_API)))] use std::ops::*; use std::os::raw::c_double; diff --git a/src/types/floatob.rs b/src/types/floatob.rs index 0b0b5341310..5b773237069 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -83,7 +83,9 @@ impl<'source> FromPyObject<'source> for f32 { mod test { #[cfg(not(Py_LIMITED_API))] use crate::ffi::PyFloat_AS_DOUBLE; - use crate::{AsPyPointer, Python, ToPyObject}; + #[cfg(not(Py_LIMITED_API))] + use crate::AsPyPointer; + use crate::{Python, ToPyObject}; macro_rules! num_to_py_object_and_back ( ($func_name:ident, $t1:ty, $t2:ty) => ( diff --git a/src/types/function.rs b/src/types/function.rs index 05f001fe521..832a52b9b73 100644 --- a/src/types/function.rs +++ b/src/types/function.rs @@ -96,4 +96,5 @@ impl PyCFunction { #[repr(transparent)] pub struct PyFunction(PyAny); +#[cfg(not(Py_LIMITED_API))] pyobject_native_var_type!(PyFunction, ffi::PyFunction_Type, ffi::PyFunction_Check); diff --git a/src/types/iterator.rs b/src/types/iterator.rs index cc798ebfd6a..ea0f2dc9321 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -2,9 +2,9 @@ // // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython -use crate::{ - ffi, AsPyPointer, PyAny, PyDowncastError, PyErr, PyNativeType, PyResult, PyTryFrom, Python, -}; +use crate::{ffi, AsPyPointer, PyAny, PyErr, PyNativeType, PyResult, Python}; +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] +use crate::{PyDowncastError, PyTryFrom}; /// A Python iterator object. /// @@ -28,6 +28,7 @@ use crate::{ #[repr(transparent)] pub struct PyIterator(PyAny); pyobject_native_type_named!(PyIterator); +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] pyobject_native_type_extract!(PyIterator); impl PyIterator { @@ -67,6 +68,8 @@ impl<'p> Iterator for &'p PyIterator { } } +// PyIter_Check does not exist in the limited API until 3.8 +#[cfg(any(not(Py_LIMITED_API), Py_3_8))] impl<'v> PyTryFrom<'v> for PyIterator { fn try_from>(value: V) -> Result<&'v PyIterator, PyDowncastError<'v>> { let value = value.into(); @@ -96,7 +99,9 @@ mod tests { use crate::exceptions::PyTypeError; use crate::gil::GILPool; use crate::types::{PyDict, PyList}; - use crate::{Py, PyAny, PyTryFrom, Python, ToPyObject}; + #[cfg(any(not(Py_LIMITED_API), Py_3_8))] + use crate::{Py, PyAny, PyTryFrom}; + use crate::{Python, ToPyObject}; use indoc::indoc; #[test] @@ -200,6 +205,7 @@ mod tests { } #[test] + #[cfg(any(not(Py_LIMITED_API), Py_3_8))] fn iterator_try_from() { let gil_guard = Python::acquire_gil(); let py = gil_guard.python(); diff --git a/src/types/mod.rs b/src/types/mod.rs index 3cccd86ec9a..7e532645a38 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -106,6 +106,8 @@ macro_rules! pyobject_native_type_core { #[macro_export] macro_rules! pyobject_native_type_sized { ($name: ty, $layout: path $(,$type_param: ident)*) => { + // To prevent inheriting native types with ABI3 + #[cfg(not(Py_LIMITED_API))] impl $crate::type_object::PySizedLayout<$name> for $layout {} impl<'a, $($type_param,)*> $crate::derive_utils::PyBaseTypeUtils for $name { type Dict = $crate::pyclass_slots::PyClassDummySlot; diff --git a/src/types/set.rs b/src/types/set.rs index 09417534b7e..5593490afd8 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -2,6 +2,8 @@ // use crate::err::{self, PyErr, PyResult}; +#[cfg(Py_LIMITED_API)] +use crate::types::PyIterator; use crate::{ ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyNativeType, PyObject, Python, ToBorrowedObject, ToPyObject, @@ -110,21 +112,48 @@ impl PySet { /// Returns an iterator of values in this set. /// /// Note that it can be unsafe to use when the set might be changed by other code. - #[cfg(not(Py_LIMITED_API))] pub fn iter(&self) -> PySetIterator { + PySetIterator::new(self) + } +} + +#[cfg(Py_LIMITED_API)] +pub struct PySetIterator<'p> { + it: &'p PyIterator, +} + +#[cfg(Py_LIMITED_API)] +impl PySetIterator<'_> { + fn new(set: &PyAny) -> PySetIterator { PySetIterator { - set: self.as_ref(), - pos: 0, + it: PyIterator::from_object(set.py(), set).unwrap(), } } } +#[cfg(Py_LIMITED_API)] +impl<'py> Iterator for PySetIterator<'py> { + type Item = &'py super::PyAny; + + #[inline] + fn next(&mut self) -> Option { + self.it.next().map(|p| p.unwrap()) + } +} + #[cfg(not(Py_LIMITED_API))] pub struct PySetIterator<'py> { set: &'py super::PyAny, pos: isize, } +#[cfg(not(Py_LIMITED_API))] +impl PySetIterator<'_> { + fn new(set: &PyAny) -> PySetIterator { + PySetIterator { set, pos: 0 } + } +} + #[cfg(not(Py_LIMITED_API))] impl<'py> Iterator for PySetIterator<'py> { type Item = &'py super::PyAny; @@ -145,7 +174,6 @@ impl<'py> Iterator for PySetIterator<'py> { } } -#[cfg(not(Py_LIMITED_API))] impl<'a> std::iter::IntoIterator for &'a PySet { type Item = &'a PyAny; type IntoIter = PySetIterator<'a>; @@ -281,22 +309,17 @@ impl PyFrozenSet { /// Returns an iterator of values in this frozen set. /// /// Note that it can be unsafe to use when the set might be changed by other code. - #[cfg(not(Py_LIMITED_API))] pub fn iter(&self) -> PySetIterator { - self.into_iter() + PySetIterator::new(self.as_ref()) } } -#[cfg(not(Py_LIMITED_API))] impl<'a> std::iter::IntoIterator for &'a PyFrozenSet { type Item = &'a PyAny; type IntoIter = PySetIterator<'a>; fn into_iter(self) -> Self::IntoIter { - PySetIterator { - set: self.as_ref(), - pos: 0, - } + self.iter() } } diff --git a/src/types/string.rs b/src/types/string.rs index 8e313edc848..2bb5e3cd290 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -44,6 +44,7 @@ impl PyString { /// (containing unpaired surrogates). #[inline] pub fn to_str(&self) -> PyResult<&str> { + #[cfg(not(Py_LIMITED_API))] unsafe { let mut size: ffi::Py_ssize_t = 0; let data = ffi::PyUnicode_AsUTF8AndSize(self.as_ptr(), &mut size) as *const u8; @@ -54,6 +55,16 @@ impl PyString { Ok(std::str::from_utf8_unchecked(slice)) } } + #[cfg(Py_LIMITED_API)] + unsafe { + let data = ffi::PyUnicode_AsUTF8String(self.as_ptr()); + if data.is_null() { + Err(PyErr::fetch(self.py())) + } else { + let bytes = self.py().from_owned_ptr::(data); + Ok(std::str::from_utf8_unchecked(bytes.as_bytes())) + } + } } /// Converts the `PyString` into a Rust string. diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 77e7c903912..9dbe9395164 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -5,7 +5,6 @@ use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyNativeType, PyObject, PyResult, PyTryFrom, Python, ToPyObject, }; -use std::slice; /// Represents a Python `tuple` object. /// @@ -87,7 +86,7 @@ impl PyTuple { // and because tuples are immutable. unsafe { let ptr = self.as_ptr() as *mut ffi::PyTupleObject; - let slice = slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len()); + let slice = std::slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len()); &*(slice as *const [*mut ffi::PyObject] as *const [&PyAny]) } } diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 7db9d6c347c..30037a5c3ee 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -6,8 +6,6 @@ use crate::err::{PyErr, PyResult}; use crate::instance::PyNativeType; use crate::type_object::PyTypeObject; use crate::{ffi, AsPyPointer, PyAny, Python}; -use std::borrow::Cow; -use std::ffi::CStr; /// Represents a reference to a Python `type object`. #[repr(transparent)] @@ -39,8 +37,8 @@ impl PyType { } /// Gets the name of the `PyType`. - pub fn name(&self) -> Cow { - unsafe { CStr::from_ptr((*self.as_type_ptr()).tp_name).to_string_lossy() } + pub fn name(&self) -> PyResult<&str> { + self.getattr("__qualname__")?.extract() } /// Checks whether `self` is subclass of type `T`. diff --git a/tests/test_buffer_protocol.rs b/tests/test_buffer_protocol.rs index aca7ef1ca4e..c40e751b1e8 100644 --- a/tests/test_buffer_protocol.rs +++ b/tests/test_buffer_protocol.rs @@ -1,3 +1,5 @@ +#![cfg(not(Py_LIMITED_API))] + use pyo3::buffer::PyBuffer; use pyo3::class::PyBufferProtocol; use pyo3::exceptions::PyBufferError; diff --git a/tests/test_class_attributes.rs b/tests/test_class_attributes.rs index 752935d76e5..e4893d631d6 100644 --- a/tests/test_class_attributes.rs +++ b/tests/test_class_attributes.rs @@ -51,7 +51,10 @@ fn class_attributes() { py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'"); } +// Ignored because heap types are not immutable: +// https://github.com/python/cpython/blob/master/Objects/typeobject.c#L3399-L3409 #[test] +#[ignore] fn class_attributes_are_immutable() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 649944a10f9..4c024ece2c2 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -119,7 +119,11 @@ fn test_raw_idents() { #[pyclass] struct EmptyClassInModule {} +// Ignored because heap types do not show up as being in builtins, instead they +// raise AttributeError: +// https://github.com/python/cpython/blob/master/Objects/typeobject.c#L544-L573 #[test] +#[ignore] fn empty_class_in_module() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -165,7 +169,7 @@ fn class_with_object_field() { py_assert!(py, ty, "ty(None).value == None"); } -#[pyclass(unsendable)] +#[pyclass(unsendable, subclass)] struct UnsendableBase { value: std::rc::Rc, } diff --git a/tests/test_class_conversion.rs b/tests/test_class_conversion.rs index 2c2d3030828..d379f774e0c 100644 --- a/tests/test_class_conversion.rs +++ b/tests/test_class_conversion.rs @@ -30,7 +30,7 @@ fn test_cloneable_pyclass() { assert_eq!(&c, &*mrc); } -#[pyclass] +#[pyclass(subclass)] #[derive(Default)] struct BaseClass { value: i32, diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 3cf71bd0980..2473590f553 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -26,6 +26,8 @@ fn test_compile_errors() { t.compile_fail("tests/ui/invalid_pymethod_receiver.rs"); t.compile_fail("tests/ui/invalid_result_conversion.rs"); t.compile_fail("tests/ui/missing_clone.rs"); + #[cfg(Py_LIMITED_API)] + t.compile_fail("tests/ui/abi3_nativetype_inheritance.rs"); } #[rustversion::before(1.46)] fn tests_rust_1_46(_t: &trybuild::TestCases) {} diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 2633aff43ef..e229e448181 100644 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -453,10 +453,11 @@ fn test_cls_impl() { .unwrap(); } -#[pyclass(dict)] +#[pyclass(dict, subclass)] struct DunderDictSupport {} #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -472,6 +473,7 @@ fn dunder_dict_support() { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn access_dunder_dict() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -493,6 +495,7 @@ struct InheritDict { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn inherited_dict() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -511,6 +514,7 @@ fn inherited_dict() { struct WeakRefDunderDictSupport {} #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn weakref_dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 6da23dc5fb1..eb982887b93 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -146,10 +146,11 @@ fn gc_integration2() { py_run!(py, inst, "import gc; assert inst in gc.get_objects()"); } -#[pyclass(weakref)] +#[pyclass(weakref, subclass)] struct WeakRefSupport {} #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn weakref_support() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -168,6 +169,7 @@ struct InheritWeakRef { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn inherited_weakref() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -179,7 +181,7 @@ fn inherited_weakref() { ); } -#[pyclass] +#[pyclass(subclass)] struct BaseClassWithDrop { data: Option>, } @@ -269,6 +271,16 @@ impl PyGCProtocol for TraversableClass { } } +#[cfg(PyPy)] +unsafe fn get_type_traverse(tp: *mut pyo3::ffi::PyTypeObject) -> Option { + (*tp).tp_traverse +} + +#[cfg(not(PyPy))] +unsafe fn get_type_traverse(tp: *mut pyo3::ffi::PyTypeObject) -> Option { + std::mem::transmute(pyo3::ffi::PyType_GetSlot(tp, pyo3::ffi::Py_tp_traverse)) +} + #[test] fn gc_during_borrow() { let gil = Python::acquire_gil(); @@ -285,7 +297,7 @@ fn gc_during_borrow() { // get the traverse function let ty = TraversableClass::type_object(py).as_type_ptr(); - let traverse = (*ty).tp_traverse.unwrap(); + let traverse = get_type_traverse(ty).unwrap(); // create an object and check that traversing it works normally // when it's not borrowed diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 4c346563e87..da9ab298e0d 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -3,10 +3,9 @@ use pyo3::py_run; use pyo3::types::IntoPyDict; -use pyo3::types::{PyDict, PySet}; mod common; -#[pyclass] +#[pyclass(subclass)] struct BaseClass { #[pyo3(get)] val1: usize, @@ -106,7 +105,7 @@ fn mutation_fails() { ) } -#[pyclass] +#[pyclass(subclass)] struct BaseClassWithResult { _val: usize, } @@ -153,56 +152,63 @@ except Exception as e: ); } -#[pyclass(extends=PySet)] -#[derive(Debug)] -struct SetWithName { - #[pyo3(get(name))] - _name: &'static str, -} +// Subclassing builtin types is not allowed in the LIMITED API. +#[cfg(not(Py_LIMITED_API))] +mod inheriting_native_type { + use super::*; + use pyo3::types::{PyDict, PySet}; -#[pymethods] -impl SetWithName { - #[new] - fn new() -> Self { - SetWithName { _name: "Hello :)" } + #[pyclass(extends=PySet)] + #[derive(Debug)] + struct SetWithName { + #[pyo3(get(name))] + _name: &'static str, } -} -#[test] -fn inherit_set() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let set_sub = pyo3::PyCell::new(py, SetWithName::new()).unwrap(); - py_run!( - py, - set_sub, - r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""# - ); -} + #[pymethods] + impl SetWithName { + #[new] + fn new() -> Self { + SetWithName { _name: "Hello :)" } + } + } -#[pyclass(extends=PyDict)] -#[derive(Debug)] -struct DictWithName { - #[pyo3(get(name))] - _name: &'static str, -} + #[test] + fn inherit_set() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set_sub = pyo3::PyCell::new(py, SetWithName::new()).unwrap(); + py_run!( + py, + set_sub, + r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""# + ); + } -#[pymethods] -impl DictWithName { - #[new] - fn new() -> Self { - DictWithName { _name: "Hello :)" } + #[pyclass(extends=PyDict)] + #[derive(Debug)] + struct DictWithName { + #[pyo3(get(name))] + _name: &'static str, } -} -#[test] -fn inherit_dict() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let dict_sub = pyo3::PyCell::new(py, DictWithName::new()).unwrap(); - py_run!( - py, - dict_sub, - r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub._name == "Hello :)""# - ); + #[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::PyCell::new(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_methods.rs b/tests/test_methods.rs index b61ed09cf84..9c62b3f09b8 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -76,7 +76,7 @@ impl ClassMethod { #[classmethod] /// Test class method. fn method(cls: &PyType) -> PyResult { - Ok(format!("{}.method()!", cls.name())) + Ok(format!("{}.method()!", cls.name()?)) } } @@ -104,7 +104,7 @@ struct ClassMethodWithArgs {} impl ClassMethodWithArgs { #[classmethod] fn method(cls: &PyType, input: &PyString) -> PyResult { - Ok(format!("{}.method({})", cls.name(), input)) + Ok(format!("{}.method({})", cls.name()?, input)) } } diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index 1e0889558ea..099e8cbf5ad 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -1,6 +1,9 @@ +#[cfg(not(Py_LIMITED_API))] use pyo3::buffer::PyBuffer; use pyo3::prelude::*; -use pyo3::types::{PyCFunction, PyFunction}; +use pyo3::types::PyCFunction; +#[cfg(not(Py_LIMITED_API))] +use pyo3::types::PyFunction; use pyo3::{raw_pycfunction, wrap_pyfunction}; mod common; @@ -23,6 +26,7 @@ fn test_optional_bool() { py_assert!(py, f, "f(None) == 'None'"); } +#[cfg(not(Py_LIMITED_API))] #[pyfunction] fn buffer_inplace_add(py: Python, x: PyBuffer, y: PyBuffer) { let x = x.as_mut_slice(py).unwrap(); @@ -33,6 +37,7 @@ fn buffer_inplace_add(py: Python, x: PyBuffer, y: PyBuffer) { } } +#[cfg(not(Py_LIMITED_API))] #[test] fn test_buffer_add() { let gil = Python::acquire_gil(); @@ -64,6 +69,7 @@ assert a, array.array("i", [2, 4, 6, 8]) ); } +#[cfg(not(Py_LIMITED_API))] #[pyfunction] fn function_with_pyfunction_arg(fun: &PyFunction) -> PyResult<&PyAny> { fun.call((), None) @@ -78,21 +84,31 @@ fn function_with_pycfunction_arg(fun: &PyCFunction) -> PyResult<&PyAny> { fn test_functions_with_function_args() { let gil = Python::acquire_gil(); let py = gil.python(); - let py_func_arg = wrap_pyfunction!(function_with_pyfunction_arg)(py).unwrap(); let py_cfunc_arg = wrap_pyfunction!(function_with_pycfunction_arg)(py).unwrap(); let bool_to_string = wrap_pyfunction!(optional_bool)(py).unwrap(); pyo3::py_run!( py, - py_func_arg py_cfunc_arg bool_to_string, r#" - def foo(): return "bar" - assert py_func_arg(foo) == "bar" assert py_cfunc_arg(bool_to_string) == "Some(true)" "# - ) + ); + + #[cfg(not(Py_LIMITED_API))] + { + let py_func_arg = wrap_pyfunction!(function_with_pyfunction_arg)(py).unwrap(); + + pyo3::py_run!( + py, + py_func_arg, + r#" + def foo(): return "bar" + assert py_func_arg(foo) == "bar" + "# + ); + } } #[test] diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index e81260811e5..3c9abdba83e 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -32,6 +32,7 @@ fn class_with_docs() { } #[test] +#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)] fn class_with_docs_and_signature() { /// docs line1 #[pyclass] @@ -67,6 +68,7 @@ fn class_with_docs_and_signature() { } #[test] +#[cfg_attr(all(Py_LIMITED_API, not(Py_3_10)), ignore)] fn class_with_signature() { #[pyclass] #[text_signature = "(a, b=None, *, c=42)"] @@ -86,7 +88,11 @@ fn class_with_signature() { let py = gil.python(); let typeobj = py.get_type::(); - py_assert!(py, typeobj, "typeobj.__doc__ is None"); + py_assert!( + py, + typeobj, + "typeobj.__doc__ is None or typeobj.__doc__ == ''" + ); py_assert!( py, typeobj, diff --git a/tests/test_unsendable_dict.rs b/tests/test_unsendable_dict.rs index 194b5888f83..86c974135d8 100644 --- a/tests/test_unsendable_dict.rs +++ b/tests/test_unsendable_dict.rs @@ -13,6 +13,7 @@ impl UnsendableDictClass { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn test_unsendable_dict() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -32,6 +33,7 @@ impl UnsendableDictClassWithWeakRef { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn test_unsendable_dict_with_weakref() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_various.rs b/tests/test_various.rs index 8a80df7d251..25c5e7bcb97 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -149,6 +149,7 @@ fn add_module(py: Python, module: &PyModule) -> PyResult<()> { } #[test] +#[cfg_attr(Py_LIMITED_API, ignore)] fn test_pickle() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/ui/abi3_nativetype_inheritance.rs b/tests/ui/abi3_nativetype_inheritance.rs new file mode 100644 index 00000000000..80faff1b739 --- /dev/null +++ b/tests/ui/abi3_nativetype_inheritance.rs @@ -0,0 +1,8 @@ +//! With abi3, we cannot inherit native types. +use pyo3::prelude::*; +use pyo3::types::PyDict; + +#[pyclass(extends=PyDict)] +struct TestClass {} + +fn main() {} diff --git a/tests/ui/abi3_nativetype_inheritance.stderr b/tests/ui/abi3_nativetype_inheritance.stderr new file mode 100644 index 00000000000..9787700b4a3 --- /dev/null +++ b/tests/ui/abi3_nativetype_inheritance.stderr @@ -0,0 +1,13 @@ +error[E0277]: the trait bound `pyo3::ffi::PyDictObject: pyo3::type_object::PySizedLayout` is not satisfied + --> $DIR/abi3_nativetype_inheritance.rs:5:1 + | +5 | #[pyclass(extends=PyDict)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `pyo3::type_object::PySizedLayout` is not implemented for `pyo3::ffi::PyDictObject` + | + ::: $WORKSPACE/src/type_object.rs:96:22 + | +96 | type BaseLayout: PySizedLayout; + | ----------------------------- required by this bound in `pyo3::PyTypeInfo` + | + = note: required because of the requirements on the impl of `pyo3::type_object::PySizedLayout` for `pyo3::pycell::PyCellBase` + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)