pyproto: split into new feature
This commit is contained in:
parent
6db7dd76d3
commit
558549e1c2
|
@ -52,7 +52,7 @@ serde_json = "1.0.61"
|
||||||
pyo3-build-config = { path = "pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
|
pyo3-build-config = { path = "pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["macros"]
|
default = ["macros", "pyproto"]
|
||||||
|
|
||||||
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
|
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
|
||||||
macros = ["pyo3-macros", "indoc", "unindent"]
|
macros = ["pyo3-macros", "indoc", "unindent"]
|
||||||
|
@ -60,6 +60,9 @@ macros = ["pyo3-macros", "indoc", "unindent"]
|
||||||
# Enables multiple #[pymethods] per #[pyclass]
|
# Enables multiple #[pymethods] per #[pyclass]
|
||||||
multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
|
multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
|
||||||
|
|
||||||
|
# Enables deprecated #[pyproto] macro
|
||||||
|
pyproto = ["pyo3-macros/pyproto"]
|
||||||
|
|
||||||
# Use this feature when building an extension module.
|
# Use this feature when building an extension module.
|
||||||
# It tells the linker to keep the python symbols unresolved,
|
# It tells the linker to keep the python symbols unresolved,
|
||||||
# so that the module can also be used with statically linked python interpreters.
|
# so that the module can also be used with statically linked python interpreters.
|
||||||
|
@ -83,7 +86,7 @@ nightly = []
|
||||||
|
|
||||||
# Activates all additional features
|
# Activates all additional features
|
||||||
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
|
# This is mostly intended for testing purposes - activating *all* of these isn't particularly useful.
|
||||||
full = ["macros", "multiple-pymethods", "num-bigint", "num-complex", "hashbrown", "serde", "indexmap", "eyre", "anyhow"]
|
full = ["macros", "pyproto", "multiple-pymethods", "num-bigint", "num-complex", "hashbrown", "serde", "indexmap", "eyre", "anyhow"]
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bench_call"
|
name = "bench_call"
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -3,9 +3,6 @@
|
||||||
ALL_ADDITIVE_FEATURES = macros multiple-pymethods num-bigint num-complex hashbrown serde indexmap eyre anyhow
|
ALL_ADDITIVE_FEATURES = macros multiple-pymethods num-bigint num-complex hashbrown serde indexmap eyre anyhow
|
||||||
COVERAGE_PACKAGES = --package pyo3 --package pyo3-build-config --package pyo3-macros-backend --package pyo3-macros
|
COVERAGE_PACKAGES = --package pyo3 --package pyo3-build-config --package pyo3-macros-backend --package pyo3-macros
|
||||||
|
|
||||||
list_all_additive_features:
|
|
||||||
@echo $(ALL_ADDITIVE_FEATURES)
|
|
||||||
|
|
||||||
test: lint test_py
|
test: lint test_py
|
||||||
cargo test
|
cargo test
|
||||||
cargo test --features="abi3"
|
cargo test --features="abi3"
|
||||||
|
|
|
@ -849,17 +849,17 @@ impl pyo3::IntoPy<PyObject> for MyClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pyo3::class::impl_::PyClassImpl for MyClass {
|
impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
|
||||||
const DOC: &'static str = "Class for demonstration\u{0}";
|
const DOC: &'static str = "Class for demonstration\u{0}";
|
||||||
const IS_GC: bool = false;
|
const IS_GC: bool = false;
|
||||||
const IS_BASETYPE: bool = false;
|
const IS_BASETYPE: bool = false;
|
||||||
const IS_SUBCLASS: bool = false;
|
const IS_SUBCLASS: bool = false;
|
||||||
type Layout = PyCell<MyClass>;
|
type Layout = PyCell<MyClass>;
|
||||||
type BaseType = PyAny;
|
type BaseType = PyAny;
|
||||||
type ThreadChecker = pyo3::class::impl_::ThreadCheckerStub<MyClass>;
|
type ThreadChecker = pyo3::impl_::pyclass::ThreadCheckerStub<MyClass>;
|
||||||
|
|
||||||
fn for_each_method_def(visitor: &mut dyn FnMut(&[pyo3::class::PyMethodDefType])) {
|
fn for_each_method_def(visitor: &mut dyn FnMut(&[pyo3::class::PyMethodDefType])) {
|
||||||
use pyo3::class::impl_::*;
|
use pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<MyClass>::new();
|
let collector = PyClassImplCollector::<MyClass>::new();
|
||||||
visitor(collector.py_methods());
|
visitor(collector.py_methods());
|
||||||
visitor(collector.py_class_descriptors());
|
visitor(collector.py_class_descriptors());
|
||||||
|
@ -870,23 +870,23 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
|
||||||
visitor(collector.number_protocol_methods());
|
visitor(collector.number_protocol_methods());
|
||||||
}
|
}
|
||||||
fn get_new() -> Option<pyo3::ffi::newfunc> {
|
fn get_new() -> Option<pyo3::ffi::newfunc> {
|
||||||
use pyo3::class::impl_::*;
|
use pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
collector.new_impl()
|
collector.new_impl()
|
||||||
}
|
}
|
||||||
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
|
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
|
||||||
use pyo3::class::impl_::*;
|
use pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
collector.alloc_impl()
|
collector.alloc_impl()
|
||||||
}
|
}
|
||||||
fn get_free() -> Option<pyo3::ffi::freefunc> {
|
fn get_free() -> Option<pyo3::ffi::freefunc> {
|
||||||
use pyo3::class::impl_::*;
|
use pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
collector.free_impl()
|
collector.free_impl()
|
||||||
}
|
}
|
||||||
fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) {
|
fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) {
|
||||||
// Implementation which uses dtolnay specialization to load all slots.
|
// Implementation which uses dtolnay specialization to load all slots.
|
||||||
use pyo3::class::impl_::*;
|
use pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
visitor(collector.object_protocol_slots());
|
visitor(collector.object_protocol_slots());
|
||||||
visitor(collector.number_protocol_slots());
|
visitor(collector.number_protocol_slots());
|
||||||
|
|
|
@ -65,6 +65,12 @@ Most users should only need a single `#[pymethods]` per `#[pyclass]`. In additio
|
||||||
|
|
||||||
See [the `#[pyclass]` implementation details](class.md#implementation-details) for more information.
|
See [the `#[pyclass]` implementation details](class.md#implementation-details) for more information.
|
||||||
|
|
||||||
|
### `pyproto`
|
||||||
|
|
||||||
|
This feature enables the `#[pyproto]` macro, which is an alternative (older, soon-to-be-deprecated) to `#[pymethods]` for defining magic methods such as `__eq__`.
|
||||||
|
|
||||||
|
> This feature is enabled by default. To disable it, set `default-features = false` for the `pyo3` entry in your Cargo.toml.
|
||||||
|
|
||||||
### `nightly`
|
### `nightly`
|
||||||
|
|
||||||
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
|
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
|
||||||
|
|
|
@ -22,3 +22,6 @@ pyo3-build-config = { path = "../pyo3-build-config", version = "0.15.1", feature
|
||||||
version = "1"
|
version = "1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
pyproto = []
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
mod attributes;
|
mod attributes;
|
||||||
|
#[cfg(feature = "pyproto")]
|
||||||
mod defs;
|
mod defs;
|
||||||
mod deprecations;
|
mod deprecations;
|
||||||
mod frompyobject;
|
mod frompyobject;
|
||||||
|
@ -16,11 +17,13 @@ mod konst;
|
||||||
mod method;
|
mod method;
|
||||||
mod module;
|
mod module;
|
||||||
mod params;
|
mod params;
|
||||||
|
#[cfg(feature = "pyproto")]
|
||||||
mod proto_method;
|
mod proto_method;
|
||||||
mod pyclass;
|
mod pyclass;
|
||||||
mod pyfunction;
|
mod pyfunction;
|
||||||
mod pyimpl;
|
mod pyimpl;
|
||||||
mod pymethod;
|
mod pymethod;
|
||||||
|
#[cfg(feature = "pyproto")]
|
||||||
mod pyproto;
|
mod pyproto;
|
||||||
mod wrap;
|
mod wrap;
|
||||||
|
|
||||||
|
@ -29,6 +32,7 @@ pub use module::{process_functions_in_module, pymodule_impl, PyModuleOptions};
|
||||||
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
|
pub use pyclass::{build_py_class, build_py_enum, PyClassArgs};
|
||||||
pub use pyfunction::{build_py_function, PyFunctionOptions};
|
pub use pyfunction::{build_py_function, PyFunctionOptions};
|
||||||
pub use pyimpl::{build_py_methods, PyClassMethodsType};
|
pub use pyimpl::{build_py_methods, PyClassMethodsType};
|
||||||
|
#[cfg(feature = "pyproto")]
|
||||||
pub use pyproto::build_py_proto;
|
pub use pyproto::build_py_proto;
|
||||||
pub use utils::get_doc;
|
pub use utils::get_doc;
|
||||||
pub use wrap::{wrap_pyfunction_impl, wrap_pymodule_impl, WrapPyFunctionArgs};
|
pub use wrap::{wrap_pyfunction_impl, wrap_pymodule_impl, WrapPyFunctionArgs};
|
||||||
|
|
|
@ -566,23 +566,23 @@ impl<'a> FnSpec<'a> {
|
||||||
let doc = &self.doc;
|
let doc = &self.doc;
|
||||||
match self.convention {
|
match self.convention {
|
||||||
CallingConvention::Noargs => quote! {
|
CallingConvention::Noargs => quote! {
|
||||||
_pyo3::class::methods::PyMethodDef::noargs(
|
_pyo3::impl_::pymethods::PyMethodDef::noargs(
|
||||||
#python_name,
|
#python_name,
|
||||||
_pyo3::class::methods::PyCFunction(#wrapper),
|
_pyo3::impl_::pymethods::PyCFunction(#wrapper),
|
||||||
#doc,
|
#doc,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
CallingConvention::Fastcall => quote! {
|
CallingConvention::Fastcall => quote! {
|
||||||
_pyo3::class::methods::PyMethodDef::fastcall_cfunction_with_keywords(
|
_pyo3::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords(
|
||||||
#python_name,
|
#python_name,
|
||||||
_pyo3::class::methods::PyCFunctionFastWithKeywords(#wrapper),
|
_pyo3::impl_::pymethods::PyCFunctionFastWithKeywords(#wrapper),
|
||||||
#doc,
|
#doc,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
CallingConvention::Varargs => quote! {
|
CallingConvention::Varargs => quote! {
|
||||||
_pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
|
_pyo3::impl_::pymethods::PyMethodDef::cfunction_with_keywords(
|
||||||
#python_name,
|
#python_name,
|
||||||
_pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
|
_pyo3::impl_::pymethods::PyCFunctionWithKeywords(#wrapper),
|
||||||
#doc,
|
#doc,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -511,11 +511,11 @@ fn unit_variants_as_descriptors<'a>(
|
||||||
.map(|var| gen_py_const(&cls_type, &variant_to_attribute(var)));
|
.map(|var| gen_py_const(&cls_type, &variant_to_attribute(var)));
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl _pyo3::class::impl_::PyClassDescriptors<#cls>
|
impl _pyo3::impl_::pyclass::PyClassDescriptors<#cls>
|
||||||
for _pyo3::class::impl_::PyClassImplCollector<#cls>
|
for _pyo3::impl_::pyclass::PyClassImplCollector<#cls>
|
||||||
{
|
{
|
||||||
fn py_class_descriptors(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
|
fn py_class_descriptors(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
|
||||||
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#py_methods),*];
|
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] = &[#(#py_methods),*];
|
||||||
METHODS
|
METHODS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,11 +574,11 @@ fn impl_descriptors(
|
||||||
.collect::<syn::Result<_>>()?;
|
.collect::<syn::Result<_>>()?;
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl _pyo3::class::impl_::PyClassDescriptors<#cls>
|
impl _pyo3::impl_::pyclass::PyClassDescriptors<#cls>
|
||||||
for _pyo3::class::impl_::PyClassImplCollector<#cls>
|
for _pyo3::impl_::pyclass::PyClassImplCollector<#cls>
|
||||||
{
|
{
|
||||||
fn py_class_descriptors(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
|
fn py_class_descriptors(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
|
||||||
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#py_methods),*];
|
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] = &[#(#py_methods),*];
|
||||||
METHODS
|
METHODS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -676,7 +676,7 @@ impl<'a> PyClassImplsBuilder<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let base_nativetype = if attr.has_extends {
|
let base_nativetype = if attr.has_extends {
|
||||||
quote! { <Self::BaseType as _pyo3::class::impl_::PyClassBaseType>::BaseNativeType }
|
quote! { <Self::BaseType as _pyo3::impl_::pyclass::PyClassBaseType>::BaseNativeType }
|
||||||
} else {
|
} else {
|
||||||
quote! { _pyo3::PyAny }
|
quote! { _pyo3::PyAny }
|
||||||
};
|
};
|
||||||
|
@ -749,13 +749,13 @@ impl<'a> PyClassImplsBuilder<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let thread_checker = if self.attr.has_unsendable {
|
let thread_checker = if self.attr.has_unsendable {
|
||||||
quote! { _pyo3::class::impl_::ThreadCheckerImpl<#cls> }
|
quote! { _pyo3::impl_::pyclass::ThreadCheckerImpl<#cls> }
|
||||||
} else if self.attr.has_extends {
|
} else if self.attr.has_extends {
|
||||||
quote! {
|
quote! {
|
||||||
_pyo3::class::impl_::ThreadCheckerInherited<#cls, <#cls as _pyo3::class::impl_::PyClassImpl>::BaseType>
|
_pyo3::impl_::pyclass::ThreadCheckerInherited<#cls, <#cls as _pyo3::impl_::pyclass::PyClassImpl>::BaseType>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! { _pyo3::class::impl_::ThreadCheckerStub<#cls> }
|
quote! { _pyo3::impl_::pyclass::ThreadCheckerStub<#cls> }
|
||||||
};
|
};
|
||||||
|
|
||||||
let (for_each_py_method, methods_protos, inventory, inventory_class) = match self
|
let (for_each_py_method, methods_protos, inventory, inventory_class) = match self
|
||||||
|
@ -775,13 +775,13 @@ impl<'a> PyClassImplsBuilder<'a> {
|
||||||
);
|
);
|
||||||
(
|
(
|
||||||
quote! {
|
quote! {
|
||||||
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::class::impl_::PyClassImpl>::Inventory>() {
|
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::impl_::pyclass::PyClassImpl>::Inventory>() {
|
||||||
visitor(_pyo3::class::impl_::PyClassInventory::methods(inventory));
|
visitor(_pyo3::impl_::pyclass::PyClassInventory::methods(inventory));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
quote! {
|
quote! {
|
||||||
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::class::impl_::PyClassImpl>::Inventory>() {
|
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::impl_::pyclass::PyClassImpl>::Inventory>() {
|
||||||
visitor(_pyo3::class::impl_::PyClassInventory::slots(inventory));
|
visitor(_pyo3::impl_::pyclass::PyClassInventory::slots(inventory));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(quote! { type Inventory = #inventory_class_name; }),
|
Some(quote! { type Inventory = #inventory_class_name; }),
|
||||||
|
@ -789,8 +789,37 @@ impl<'a> PyClassImplsBuilder<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let pyproto_method_visitors = if cfg!(feature = "pyproto") {
|
||||||
|
Some(quote! {
|
||||||
|
visitor(collector.object_protocol_methods());
|
||||||
|
visitor(collector.async_protocol_methods());
|
||||||
|
visitor(collector.descr_protocol_methods());
|
||||||
|
visitor(collector.mapping_protocol_methods());
|
||||||
|
visitor(collector.number_protocol_methods());
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let pyproto_proto_visitors = if cfg!(feature = "pyproto") {
|
||||||
|
Some(quote! {
|
||||||
|
visitor(collector.object_protocol_slots());
|
||||||
|
visitor(collector.number_protocol_slots());
|
||||||
|
visitor(collector.iter_protocol_slots());
|
||||||
|
visitor(collector.gc_protocol_slots());
|
||||||
|
visitor(collector.descr_protocol_slots());
|
||||||
|
visitor(collector.mapping_protocol_slots());
|
||||||
|
visitor(collector.sequence_protocol_slots());
|
||||||
|
visitor(collector.async_protocol_slots());
|
||||||
|
visitor(collector.buffer_protocol_slots());
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl _pyo3::class::impl_::PyClassImpl for #cls {
|
impl _pyo3::impl_::pyclass::PyClassImpl for #cls {
|
||||||
const DOC: &'static str = #doc;
|
const DOC: &'static str = #doc;
|
||||||
const IS_GC: bool = #is_gc;
|
const IS_GC: bool = #is_gc;
|
||||||
const IS_BASETYPE: bool = #is_basetype;
|
const IS_BASETYPE: bool = #is_basetype;
|
||||||
|
@ -802,48 +831,36 @@ impl<'a> PyClassImplsBuilder<'a> {
|
||||||
#inventory
|
#inventory
|
||||||
|
|
||||||
fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[_pyo3::class::PyMethodDefType])) {
|
fn for_each_method_def(visitor: &mut dyn ::std::ops::FnMut(&[_pyo3::class::PyMethodDefType])) {
|
||||||
use _pyo3::class::impl_::*;
|
use _pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
#for_each_py_method;
|
#for_each_py_method;
|
||||||
visitor(collector.py_class_descriptors());
|
visitor(collector.py_class_descriptors());
|
||||||
visitor(collector.object_protocol_methods());
|
#pyproto_method_visitors
|
||||||
visitor(collector.async_protocol_methods());
|
|
||||||
visitor(collector.descr_protocol_methods());
|
|
||||||
visitor(collector.mapping_protocol_methods());
|
|
||||||
visitor(collector.number_protocol_methods());
|
|
||||||
}
|
}
|
||||||
fn get_new() -> ::std::option::Option<_pyo3::ffi::newfunc> {
|
fn get_new() -> ::std::option::Option<_pyo3::ffi::newfunc> {
|
||||||
use _pyo3::class::impl_::*;
|
use _pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
collector.new_impl()
|
collector.new_impl()
|
||||||
}
|
}
|
||||||
fn get_alloc() -> ::std::option::Option<_pyo3::ffi::allocfunc> {
|
fn get_alloc() -> ::std::option::Option<_pyo3::ffi::allocfunc> {
|
||||||
use _pyo3::class::impl_::*;
|
use _pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
collector.alloc_impl()
|
collector.alloc_impl()
|
||||||
}
|
}
|
||||||
fn get_free() -> ::std::option::Option<_pyo3::ffi::freefunc> {
|
fn get_free() -> ::std::option::Option<_pyo3::ffi::freefunc> {
|
||||||
use _pyo3::class::impl_::*;
|
use _pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
collector.free_impl()
|
collector.free_impl()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[_pyo3::ffi::PyType_Slot])) {
|
fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[_pyo3::ffi::PyType_Slot])) {
|
||||||
// Implementation which uses dtolnay specialization to load all slots.
|
// Implementation which uses dtolnay specialization to load all slots.
|
||||||
use _pyo3::class::impl_::*;
|
use _pyo3::impl_::pyclass::*;
|
||||||
let collector = PyClassImplCollector::<Self>::new();
|
let collector = PyClassImplCollector::<Self>::new();
|
||||||
// This depends on Python implementation detail;
|
// This depends on Python implementation detail;
|
||||||
// an old slot entry will be overriden by newer ones.
|
// an old slot entry will be overriden by newer ones.
|
||||||
visitor(collector.py_class_default_slots());
|
visitor(collector.py_class_default_slots());
|
||||||
visitor(collector.object_protocol_slots());
|
#pyproto_proto_visitors
|
||||||
visitor(collector.number_protocol_slots());
|
|
||||||
visitor(collector.iter_protocol_slots());
|
|
||||||
visitor(collector.gc_protocol_slots());
|
|
||||||
visitor(collector.descr_protocol_slots());
|
|
||||||
visitor(collector.mapping_protocol_slots());
|
|
||||||
visitor(collector.sequence_protocol_slots());
|
|
||||||
visitor(collector.async_protocol_slots());
|
|
||||||
visitor(collector.buffer_protocol_slots());
|
|
||||||
#methods_protos
|
#methods_protos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,7 +878,7 @@ impl<'a> PyClassImplsBuilder<'a> {
|
||||||
|
|
||||||
self.attr.freelist.as_ref().map_or(quote!{}, |freelist| {
|
self.attr.freelist.as_ref().map_or(quote!{}, |freelist| {
|
||||||
quote! {
|
quote! {
|
||||||
impl _pyo3::class::impl_::PyClassWithFreeList for #cls {
|
impl _pyo3::impl_::pyclass::PyClassWithFreeList for #cls {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_free_list(_py: _pyo3::Python<'_>) -> &mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> {
|
fn get_free_list(_py: _pyo3::Python<'_>) -> &mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> {
|
||||||
static mut FREELIST: *mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> = 0 as *mut _;
|
static mut FREELIST: *mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> = 0 as *mut _;
|
||||||
|
@ -875,17 +892,17 @@ impl<'a> PyClassImplsBuilder<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl _pyo3::class::impl_::PyClassAllocImpl<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
|
impl _pyo3::impl_::pyclass::PyClassAllocImpl<#cls> for _pyo3::impl_::pyclass::PyClassImplCollector<#cls> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn alloc_impl(self) -> ::std::option::Option<_pyo3::ffi::allocfunc> {
|
fn alloc_impl(self) -> ::std::option::Option<_pyo3::ffi::allocfunc> {
|
||||||
::std::option::Option::Some(_pyo3::class::impl_::alloc_with_freelist::<#cls>)
|
::std::option::Option::Some(_pyo3::impl_::pyclass::alloc_with_freelist::<#cls>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl _pyo3::class::impl_::PyClassFreeImpl<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
|
impl _pyo3::impl_::pyclass::PyClassFreeImpl<#cls> for _pyo3::impl_::pyclass::PyClassImplCollector<#cls> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn free_impl(self) -> ::std::option::Option<_pyo3::ffi::freefunc> {
|
fn free_impl(self) -> ::std::option::Option<_pyo3::ffi::freefunc> {
|
||||||
::std::option::Option::Some(_pyo3::class::impl_::free_with_freelist::<#cls>)
|
::std::option::Option::Some(_pyo3::impl_::pyclass::free_with_freelist::<#cls>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -928,7 +945,7 @@ fn define_inventory_class(inventory_class_name: &syn::Ident) -> TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl _pyo3::class::impl_::PyClassInventory for #inventory_class_name {
|
impl _pyo3::impl_::pyclass::PyClassInventory for #inventory_class_name {
|
||||||
fn methods(&'static self) -> &'static [_pyo3::class::PyMethodDefType] {
|
fn methods(&'static self) -> &'static [_pyo3::class::PyMethodDefType] {
|
||||||
self.methods
|
self.methods
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,7 +190,7 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
|
||||||
_pyo3::class::PyMethodDefType::ClassAttribute({
|
_pyo3::class::PyMethodDefType::ClassAttribute({
|
||||||
_pyo3::class::PyClassAttributeDef::new(
|
_pyo3::class::PyClassAttributeDef::new(
|
||||||
#python_name,
|
#python_name,
|
||||||
_pyo3::class::methods::PyClassAttributeFactory({
|
_pyo3::impl_::pymethods::PyClassAttributeFactory({
|
||||||
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
|
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
|
||||||
#deprecations
|
#deprecations
|
||||||
_pyo3::IntoPy::into_py(#cls::#member, py)
|
_pyo3::IntoPy::into_py(#cls::#member, py)
|
||||||
|
@ -234,8 +234,8 @@ pub fn gen_default_slot_impls(cls: &syn::Ident, method_defs: Vec<TokenStream>) -
|
||||||
impl #cls {
|
impl #cls {
|
||||||
#(#method_defs)*
|
#(#method_defs)*
|
||||||
}
|
}
|
||||||
impl ::pyo3::class::impl_::PyClassDefaultSlots<#cls>
|
impl ::pyo3::impl_::pyclass::PyClassDefaultSlots<#cls>
|
||||||
for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
|
for ::pyo3::impl_::pyclass::PyClassImplCollector<#cls> {
|
||||||
fn py_class_default_slots(self) -> &'static [::pyo3::ffi::PyType_Slot] {
|
fn py_class_default_slots(self) -> &'static [::pyo3::ffi::PyType_Slot] {
|
||||||
&[#(#proto_impls),*]
|
&[#(#proto_impls),*]
|
||||||
}
|
}
|
||||||
|
@ -245,11 +245,11 @@ pub fn gen_default_slot_impls(cls: &syn::Ident, method_defs: Vec<TokenStream>) -
|
||||||
|
|
||||||
fn impl_py_methods(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream {
|
fn impl_py_methods(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream {
|
||||||
quote! {
|
quote! {
|
||||||
impl _pyo3::class::impl_::PyMethods<#ty>
|
impl _pyo3::impl_::pyclass::PyMethods<#ty>
|
||||||
for _pyo3::class::impl_::PyClassImplCollector<#ty>
|
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
|
||||||
{
|
{
|
||||||
fn py_methods(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
|
fn py_methods(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
|
||||||
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#methods),*];
|
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] = &[#(#methods),*];
|
||||||
METHODS
|
METHODS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ fn add_shared_proto_slots(
|
||||||
let first_implemented = implemented_proto_fragments.remove($first);
|
let first_implemented = implemented_proto_fragments.remove($first);
|
||||||
let second_implemented = implemented_proto_fragments.remove($second);
|
let second_implemented = implemented_proto_fragments.remove($second);
|
||||||
if first_implemented || second_implemented {
|
if first_implemented || second_implemented {
|
||||||
proto_impls.push(quote! { _pyo3::class::impl_::$slot!(#ty) })
|
proto_impls.push(quote! { _pyo3::impl_::pyclass::$slot!(#ty) })
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -300,8 +300,8 @@ fn add_shared_proto_slots(
|
||||||
|
|
||||||
fn impl_protos(ty: &syn::Type, proto_impls: Vec<TokenStream>) -> TokenStream {
|
fn impl_protos(ty: &syn::Type, proto_impls: Vec<TokenStream>) -> TokenStream {
|
||||||
quote! {
|
quote! {
|
||||||
impl _pyo3::class::impl_::PyMethodsProtocolSlots<#ty>
|
impl _pyo3::impl_::pyclass::PyMethodsProtocolSlots<#ty>
|
||||||
for _pyo3::class::impl_::PyClassImplCollector<#ty>
|
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
|
||||||
{
|
{
|
||||||
fn methods_protocol_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
|
fn methods_protocol_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
|
||||||
&[#(#proto_impls),*]
|
&[#(#proto_impls),*]
|
||||||
|
@ -317,7 +317,7 @@ fn submit_methods_inventory(
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
quote! {
|
quote! {
|
||||||
_pyo3::inventory::submit! {
|
_pyo3::inventory::submit! {
|
||||||
type Inventory = <#ty as _pyo3::class::impl_::PyClassImpl>::Inventory;
|
type Inventory = <#ty as _pyo3::impl_::pyclass::PyClassImpl>::Inventory;
|
||||||
Inventory::new(&[#(#methods),*], &[#(#proto_impls),*])
|
Inventory::new(&[#(#methods),*], &[#(#proto_impls),*])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,7 +194,7 @@ fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream>
|
||||||
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
|
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
|
||||||
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
|
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl _pyo3::class::impl_::PyClassNewImpl<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
|
impl _pyo3::impl_::pyclass::PyClassNewImpl<#cls> for _pyo3::impl_::pyclass::PyClassImplCollector<#cls> {
|
||||||
fn new_impl(self) -> ::std::option::Option<_pyo3::ffi::newfunc> {
|
fn new_impl(self) -> ::std::option::Option<_pyo3::ffi::newfunc> {
|
||||||
::std::option::Option::Some({
|
::std::option::Option::Some({
|
||||||
#wrapper
|
#wrapper
|
||||||
|
@ -229,7 +229,7 @@ fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
|
||||||
_pyo3::class::PyMethodDefType::ClassAttribute({
|
_pyo3::class::PyMethodDefType::ClassAttribute({
|
||||||
_pyo3::class::PyClassAttributeDef::new(
|
_pyo3::class::PyClassAttributeDef::new(
|
||||||
#python_name,
|
#python_name,
|
||||||
_pyo3::class::methods::PyClassAttributeFactory({
|
_pyo3::impl_::pymethods::PyClassAttributeFactory({
|
||||||
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
|
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
|
||||||
#deprecations
|
#deprecations
|
||||||
_pyo3::IntoPy::into_py(#cls::#name(), py)
|
_pyo3::IntoPy::into_py(#cls::#name(), py)
|
||||||
|
@ -299,7 +299,7 @@ pub fn impl_py_setter_def(cls: &syn::Type, property_type: PropertyType) -> Resul
|
||||||
#deprecations
|
#deprecations
|
||||||
_pyo3::class::PySetterDef::new(
|
_pyo3::class::PySetterDef::new(
|
||||||
#python_name,
|
#python_name,
|
||||||
_pyo3::class::methods::PySetter({
|
_pyo3::impl_::pymethods::PySetter({
|
||||||
unsafe extern "C" fn __wrap(
|
unsafe extern "C" fn __wrap(
|
||||||
_slf: *mut _pyo3::ffi::PyObject,
|
_slf: *mut _pyo3::ffi::PyObject,
|
||||||
_value: *mut _pyo3::ffi::PyObject,
|
_value: *mut _pyo3::ffi::PyObject,
|
||||||
|
@ -379,7 +379,7 @@ pub fn impl_py_getter_def(cls: &syn::Type, property_type: PropertyType) -> Resul
|
||||||
#deprecations
|
#deprecations
|
||||||
_pyo3::class::PyGetterDef::new(
|
_pyo3::class::PyGetterDef::new(
|
||||||
#python_name,
|
#python_name,
|
||||||
_pyo3::class::methods::PyGetter({
|
_pyo3::impl_::pymethods::PyGetter({
|
||||||
unsafe extern "C" fn __wrap(
|
unsafe extern "C" fn __wrap(
|
||||||
_slf: *mut _pyo3::ffi::PyObject,
|
_slf: *mut _pyo3::ffi::PyObject,
|
||||||
_: *mut ::std::os::raw::c_void
|
_: *mut ::std::os::raw::c_void
|
||||||
|
@ -972,7 +972,7 @@ impl SlotFragmentDef {
|
||||||
let body = generate_method_body(cls, spec, &py, arguments, *extract_error_mode, None)?;
|
let body = generate_method_body(cls, spec, &py, arguments, *extract_error_mode, None)?;
|
||||||
let ret_ty = ret_ty.ffi_type();
|
let ret_ty = ret_ty.ffi_type();
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl _pyo3::class::impl_::#fragment_trait<#cls> for _pyo3::class::impl_::PyClassImplCollector<#cls> {
|
impl _pyo3::impl_::pyclass::#fragment_trait<#cls> for _pyo3::impl_::pyclass::PyClassImplCollector<#cls> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn #method(
|
unsafe fn #method(
|
||||||
|
|
|
@ -109,11 +109,11 @@ fn impl_normal_methods(
|
||||||
let methods_trait = proto.methods_trait();
|
let methods_trait = proto.methods_trait();
|
||||||
let methods_trait_methods = proto.methods_trait_methods();
|
let methods_trait_methods = proto.methods_trait_methods();
|
||||||
quote! {
|
quote! {
|
||||||
impl _pyo3::class::impl_::#methods_trait<#ty>
|
impl _pyo3::impl_::pyclass::#methods_trait<#ty>
|
||||||
for _pyo3::class::impl_::PyClassImplCollector<#ty>
|
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
|
||||||
{
|
{
|
||||||
fn #methods_trait_methods(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
|
fn #methods_trait_methods(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
|
||||||
static METHODS: &[_pyo3::class::methods::PyMethodDefType] =
|
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] =
|
||||||
&[#(#py_methods),*];
|
&[#(#py_methods),*];
|
||||||
METHODS
|
METHODS
|
||||||
}
|
}
|
||||||
|
@ -153,8 +153,8 @@ fn impl_proto_methods(
|
||||||
}
|
}
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl _pyo3::class::impl_::#slots_trait<#ty>
|
impl _pyo3::impl_::pyclass::#slots_trait<#ty>
|
||||||
for _pyo3::class::impl_::PyClassImplCollector<#ty>
|
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
|
||||||
{
|
{
|
||||||
fn #slots_trait_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
|
fn #slots_trait_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
|
||||||
&[#(#tokens),*]
|
&[#(#tokens),*]
|
||||||
|
|
|
@ -16,6 +16,8 @@ proc-macro = true
|
||||||
[features]
|
[features]
|
||||||
multiple-pymethods = []
|
multiple-pymethods = []
|
||||||
|
|
||||||
|
pyproto = ["pyo3-macros-backend/pyproto"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = { version = "1", default-features = false }
|
proc-macro2 = { version = "1", default-features = false }
|
||||||
quote = "1"
|
quote = "1"
|
||||||
|
|
|
@ -9,9 +9,8 @@ use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use pyo3_macros_backend::{
|
use pyo3_macros_backend::{
|
||||||
build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods,
|
build_derive_from_pyobject, build_py_class, build_py_enum, build_py_function, build_py_methods,
|
||||||
build_py_proto, get_doc, process_functions_in_module, pymodule_impl, wrap_pyfunction_impl,
|
get_doc, process_functions_in_module, pymodule_impl, wrap_pyfunction_impl, wrap_pymodule_impl,
|
||||||
wrap_pymodule_impl, PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions,
|
PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions, WrapPyFunctionArgs,
|
||||||
WrapPyFunctionArgs,
|
|
||||||
};
|
};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{parse::Nothing, parse_macro_input};
|
use syn::{parse::Nothing, parse_macro_input};
|
||||||
|
@ -68,9 +67,10 @@ pub fn pymodule(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
/// [4]: ../class/gc/trait.PyGCProtocol.html
|
/// [4]: ../class/gc/trait.PyGCProtocol.html
|
||||||
/// [5]: ../class/iter/trait.PyIterProtocol.html
|
/// [5]: ../class/iter/trait.PyIterProtocol.html
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
#[cfg(feature = "pyproto")]
|
||||||
pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let mut ast = parse_macro_input!(input as syn::ItemImpl);
|
let mut ast = parse_macro_input!(input as syn::ItemImpl);
|
||||||
let expanded = build_py_proto(&mut ast).unwrap_or_compile_error();
|
let expanded = pyo3_macros_backend::build_py_proto(&mut ast).unwrap_or_compile_error();
|
||||||
|
|
||||||
quote!(
|
quote!(
|
||||||
#ast
|
#ast
|
||||||
|
|
|
@ -11,10 +11,7 @@ impl Subclassable {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Subclassable {}
|
Subclassable {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[pyproto]
|
|
||||||
impl pyo3::PyObjectProtocol for Subclassable {
|
|
||||||
fn __str__(&self) -> PyResult<&'static str> {
|
fn __str__(&self) -> PyResult<&'static str> {
|
||||||
Ok("Subclassable")
|
Ok("Subclassable")
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,37 +12,6 @@ use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput};
|
||||||
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject};
|
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject};
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
/// Operators for the `__richcmp__` method
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum CompareOp {
|
|
||||||
/// The *less than* operator.
|
|
||||||
Lt = ffi::Py_LT as isize,
|
|
||||||
/// The *less than or equal to* operator.
|
|
||||||
Le = ffi::Py_LE as isize,
|
|
||||||
/// The equality operator.
|
|
||||||
Eq = ffi::Py_EQ as isize,
|
|
||||||
/// The *not equal to* operator.
|
|
||||||
Ne = ffi::Py_NE as isize,
|
|
||||||
/// The *greater than* operator.
|
|
||||||
Gt = ffi::Py_GT as isize,
|
|
||||||
/// The *greater than or equal to* operator.
|
|
||||||
Ge = ffi::Py_GE as isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompareOp {
|
|
||||||
pub fn from_raw(op: c_int) -> Option<Self> {
|
|
||||||
match op {
|
|
||||||
ffi::Py_LT => Some(CompareOp::Lt),
|
|
||||||
ffi::Py_LE => Some(CompareOp::Le),
|
|
||||||
ffi::Py_EQ => Some(CompareOp::Eq),
|
|
||||||
ffi::Py_NE => Some(CompareOp::Ne),
|
|
||||||
ffi::Py_GT => Some(CompareOp::Gt),
|
|
||||||
ffi::Py_GE => Some(CompareOp::Ge),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Basic Python class customization
|
/// Basic Python class customization
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait PyObjectProtocol<'p>: PyClass {
|
pub trait PyObjectProtocol<'p>: PyClass {
|
||||||
|
@ -202,3 +171,5 @@ py_func_set_del!(
|
||||||
__delattr__
|
__delattr__
|
||||||
);
|
);
|
||||||
py_unary_func!(bool, PyObjectBoolProtocol, T::__bool__, c_int);
|
py_unary_func!(bool, PyObjectBoolProtocol, T::__bool__, c_int);
|
||||||
|
|
||||||
|
pub use crate::pyclass::CompareOp;
|
||||||
|
|
|
@ -1,818 +0,0 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
exceptions::{PyAttributeError, PyNotImplementedError},
|
|
||||||
ffi,
|
|
||||||
impl_::freelist::FreeList,
|
|
||||||
pycell::PyCellLayout,
|
|
||||||
pyclass_init::PyObjectInit,
|
|
||||||
type_object::{PyLayout, PyTypeObject},
|
|
||||||
PyClass, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
marker::PhantomData,
|
|
||||||
os::raw::{c_int, c_void},
|
|
||||||
ptr::NonNull,
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// This type is used as a "dummy" type on which dtolnay specializations are
|
|
||||||
/// applied to apply implementations from `#[pymethods]` & `#[pyproto]`
|
|
||||||
pub struct PyClassImplCollector<T>(PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T> PyClassImplCollector<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(PhantomData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for PyClassImplCollector<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Clone for PyClassImplCollector<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Copy for PyClassImplCollector<T> {}
|
|
||||||
|
|
||||||
/// Implements the underlying functionality of `#[pyclass]`, assembled by various proc macros.
|
|
||||||
///
|
|
||||||
/// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail
|
|
||||||
/// and may be changed at any time.
|
|
||||||
pub trait PyClassImpl: Sized {
|
|
||||||
/// Class doc string
|
|
||||||
const DOC: &'static str = "\0";
|
|
||||||
|
|
||||||
/// #[pyclass(gc)]
|
|
||||||
const IS_GC: bool = false;
|
|
||||||
|
|
||||||
/// #[pyclass(subclass)]
|
|
||||||
const IS_BASETYPE: bool = false;
|
|
||||||
|
|
||||||
/// #[pyclass(extends=...)]
|
|
||||||
const IS_SUBCLASS: bool = false;
|
|
||||||
|
|
||||||
/// Layout
|
|
||||||
type Layout: PyLayout<Self>;
|
|
||||||
|
|
||||||
/// Base class
|
|
||||||
type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType;
|
|
||||||
|
|
||||||
/// This handles following two situations:
|
|
||||||
/// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
|
|
||||||
/// This implementation is used by default. Compile fails if `T: !Send`.
|
|
||||||
/// 2. In case `T` is `!Send`, `ThreadChecker` panics when `T` is accessed by another thread.
|
|
||||||
/// This implementation is used when `#[pyclass(unsendable)]` is given.
|
|
||||||
/// Panicking makes it safe to expose `T: !Send` to the Python interpreter, where all objects
|
|
||||||
/// can be accessed by multiple threads by `threading` module.
|
|
||||||
type ThreadChecker: PyClassThreadChecker<Self>;
|
|
||||||
|
|
||||||
#[cfg(feature = "multiple-pymethods")]
|
|
||||||
type Inventory: PyClassInventory;
|
|
||||||
|
|
||||||
fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {}
|
|
||||||
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&[ffi::PyType_Slot])) {}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_new() -> Option<ffi::newfunc> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn get_alloc() -> Option<ffi::allocfunc> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn get_free() -> Option<ffi::freefunc> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn dict_offset() -> Option<ffi::Py_ssize_t> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traits describing known special methods.
|
|
||||||
|
|
||||||
pub trait PyClassNewImpl<T> {
|
|
||||||
fn new_impl(self) -> Option<ffi::newfunc>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PyClassNewImpl<T> for &'_ PyClassImplCollector<T> {
|
|
||||||
fn new_impl(self) -> Option<ffi::newfunc> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! slot_fragment_trait {
|
|
||||||
($trait_name:ident, $($default_method:tt)*) => {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
pub trait $trait_name<T>: Sized {
|
|
||||||
$($default_method)*
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Macro which expands to three items
|
|
||||||
/// - Trait for a __setitem__ dunder
|
|
||||||
/// - Trait for the corresponding __delitem__ dunder
|
|
||||||
/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
|
|
||||||
macro_rules! define_pyclass_setattr_slot {
|
|
||||||
(
|
|
||||||
$set_trait:ident,
|
|
||||||
$del_trait:ident,
|
|
||||||
$set:ident,
|
|
||||||
$del:ident,
|
|
||||||
$set_error:expr,
|
|
||||||
$del_error:expr,
|
|
||||||
$generate_macro:ident,
|
|
||||||
$slot:ident,
|
|
||||||
$func_ty:ident,
|
|
||||||
) => {
|
|
||||||
slot_fragment_trait! {
|
|
||||||
$set_trait,
|
|
||||||
|
|
||||||
/// # Safety: _slf and _attr must be valid non-null Python objects
|
|
||||||
#[inline]
|
|
||||||
unsafe fn $set(
|
|
||||||
self,
|
|
||||||
_py: Python,
|
|
||||||
_slf: *mut ffi::PyObject,
|
|
||||||
_attr: *mut ffi::PyObject,
|
|
||||||
_value: NonNull<ffi::PyObject>,
|
|
||||||
) -> PyResult<()> {
|
|
||||||
$set_error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slot_fragment_trait! {
|
|
||||||
$del_trait,
|
|
||||||
|
|
||||||
/// # Safety: _slf and _attr must be valid non-null Python objects
|
|
||||||
#[inline]
|
|
||||||
unsafe fn $del(
|
|
||||||
self,
|
|
||||||
_py: Python,
|
|
||||||
_slf: *mut ffi::PyObject,
|
|
||||||
_attr: *mut ffi::PyObject,
|
|
||||||
) -> PyResult<()> {
|
|
||||||
$del_error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! $generate_macro {
|
|
||||||
($cls:ty) => {{
|
|
||||||
unsafe extern "C" fn __wrap(
|
|
||||||
_slf: *mut $crate::ffi::PyObject,
|
|
||||||
attr: *mut $crate::ffi::PyObject,
|
|
||||||
value: *mut $crate::ffi::PyObject,
|
|
||||||
) -> ::std::os::raw::c_int {
|
|
||||||
use ::std::option::Option::*;
|
|
||||||
use $crate::callback::IntoPyCallbackOutput;
|
|
||||||
use $crate::class::impl_::*;
|
|
||||||
$crate::callback::handle_panic(|py| {
|
|
||||||
let collector = PyClassImplCollector::<$cls>::new();
|
|
||||||
if let Some(value) = ::std::ptr::NonNull::new(value) {
|
|
||||||
collector.$set(py, _slf, attr, value).convert(py)
|
|
||||||
} else {
|
|
||||||
collector.$del(py, _slf, attr).convert(py)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
$crate::ffi::PyType_Slot {
|
|
||||||
slot: $crate::ffi::$slot,
|
|
||||||
pfunc: __wrap as $crate::ffi::$func_ty as _,
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
pub use $generate_macro;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_setattr_slot! {
|
|
||||||
PyClass__setattr__SlotFragment,
|
|
||||||
PyClass__delattr__SlotFragment,
|
|
||||||
__setattr__,
|
|
||||||
__delattr__,
|
|
||||||
Err(PyAttributeError::new_err("can't set attribute")),
|
|
||||||
Err(PyAttributeError::new_err("can't delete attribute")),
|
|
||||||
generate_pyclass_setattr_slot,
|
|
||||||
Py_tp_setattro,
|
|
||||||
setattrofunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_setattr_slot! {
|
|
||||||
PyClass__set__SlotFragment,
|
|
||||||
PyClass__delete__SlotFragment,
|
|
||||||
__set__,
|
|
||||||
__delete__,
|
|
||||||
Err(PyNotImplementedError::new_err("can't set descriptor")),
|
|
||||||
Err(PyNotImplementedError::new_err("can't delete descriptor")),
|
|
||||||
generate_pyclass_setdescr_slot,
|
|
||||||
Py_tp_descr_set,
|
|
||||||
descrsetfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_setattr_slot! {
|
|
||||||
PyClass__setitem__SlotFragment,
|
|
||||||
PyClass__delitem__SlotFragment,
|
|
||||||
__setitem__,
|
|
||||||
__delitem__,
|
|
||||||
Err(PyNotImplementedError::new_err("can't set item")),
|
|
||||||
Err(PyNotImplementedError::new_err("can't delete item")),
|
|
||||||
generate_pyclass_setitem_slot,
|
|
||||||
Py_mp_ass_subscript,
|
|
||||||
objobjargproc,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Macro which expands to three items
|
|
||||||
/// - Trait for a lhs dunder e.g. __add__
|
|
||||||
/// - Trait for the corresponding rhs e.g. __radd__
|
|
||||||
/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
|
|
||||||
macro_rules! define_pyclass_binary_operator_slot {
|
|
||||||
(
|
|
||||||
$lhs_trait:ident,
|
|
||||||
$rhs_trait:ident,
|
|
||||||
$lhs:ident,
|
|
||||||
$rhs:ident,
|
|
||||||
$generate_macro:ident,
|
|
||||||
$slot:ident,
|
|
||||||
$func_ty:ident,
|
|
||||||
) => {
|
|
||||||
slot_fragment_trait! {
|
|
||||||
$lhs_trait,
|
|
||||||
|
|
||||||
/// # Safety: _slf and _other must be valid non-null Python objects
|
|
||||||
#[inline]
|
|
||||||
unsafe fn $lhs(
|
|
||||||
self,
|
|
||||||
_py: Python,
|
|
||||||
_slf: *mut ffi::PyObject,
|
|
||||||
_other: *mut ffi::PyObject,
|
|
||||||
) -> PyResult<*mut ffi::PyObject> {
|
|
||||||
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slot_fragment_trait! {
|
|
||||||
$rhs_trait,
|
|
||||||
|
|
||||||
/// # Safety: _slf and _other must be valid non-null Python objects
|
|
||||||
#[inline]
|
|
||||||
unsafe fn $rhs(
|
|
||||||
self,
|
|
||||||
_py: Python,
|
|
||||||
_slf: *mut ffi::PyObject,
|
|
||||||
_other: *mut ffi::PyObject,
|
|
||||||
) -> PyResult<*mut ffi::PyObject> {
|
|
||||||
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! $generate_macro {
|
|
||||||
($cls:ty) => {{
|
|
||||||
unsafe extern "C" fn __wrap(
|
|
||||||
_slf: *mut $crate::ffi::PyObject,
|
|
||||||
_other: *mut $crate::ffi::PyObject,
|
|
||||||
) -> *mut $crate::ffi::PyObject {
|
|
||||||
$crate::callback::handle_panic(|py| {
|
|
||||||
use $crate::class::impl_::*;
|
|
||||||
let collector = PyClassImplCollector::<$cls>::new();
|
|
||||||
let lhs_result = collector.$lhs(py, _slf, _other)?;
|
|
||||||
if lhs_result == $crate::ffi::Py_NotImplemented() {
|
|
||||||
$crate::ffi::Py_DECREF(lhs_result);
|
|
||||||
collector.$rhs(py, _other, _slf)
|
|
||||||
} else {
|
|
||||||
::std::result::Result::Ok(lhs_result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
$crate::ffi::PyType_Slot {
|
|
||||||
slot: $crate::ffi::$slot,
|
|
||||||
pfunc: __wrap as $crate::ffi::$func_ty as _,
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
pub use $generate_macro;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__add__SlotFragment,
|
|
||||||
PyClass__radd__SlotFragment,
|
|
||||||
__add__,
|
|
||||||
__radd__,
|
|
||||||
generate_pyclass_add_slot,
|
|
||||||
Py_nb_add,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__sub__SlotFragment,
|
|
||||||
PyClass__rsub__SlotFragment,
|
|
||||||
__sub__,
|
|
||||||
__rsub__,
|
|
||||||
generate_pyclass_sub_slot,
|
|
||||||
Py_nb_subtract,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__mul__SlotFragment,
|
|
||||||
PyClass__rmul__SlotFragment,
|
|
||||||
__mul__,
|
|
||||||
__rmul__,
|
|
||||||
generate_pyclass_mul_slot,
|
|
||||||
Py_nb_multiply,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__mod__SlotFragment,
|
|
||||||
PyClass__rmod__SlotFragment,
|
|
||||||
__mod__,
|
|
||||||
__rmod__,
|
|
||||||
generate_pyclass_mod_slot,
|
|
||||||
Py_nb_remainder,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__divmod__SlotFragment,
|
|
||||||
PyClass__rdivmod__SlotFragment,
|
|
||||||
__divmod__,
|
|
||||||
__rdivmod__,
|
|
||||||
generate_pyclass_divmod_slot,
|
|
||||||
Py_nb_divmod,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__lshift__SlotFragment,
|
|
||||||
PyClass__rlshift__SlotFragment,
|
|
||||||
__lshift__,
|
|
||||||
__rlshift__,
|
|
||||||
generate_pyclass_lshift_slot,
|
|
||||||
Py_nb_lshift,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__rshift__SlotFragment,
|
|
||||||
PyClass__rrshift__SlotFragment,
|
|
||||||
__rshift__,
|
|
||||||
__rrshift__,
|
|
||||||
generate_pyclass_rshift_slot,
|
|
||||||
Py_nb_rshift,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__and__SlotFragment,
|
|
||||||
PyClass__rand__SlotFragment,
|
|
||||||
__and__,
|
|
||||||
__rand__,
|
|
||||||
generate_pyclass_and_slot,
|
|
||||||
Py_nb_and,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__or__SlotFragment,
|
|
||||||
PyClass__ror__SlotFragment,
|
|
||||||
__or__,
|
|
||||||
__ror__,
|
|
||||||
generate_pyclass_or_slot,
|
|
||||||
Py_nb_or,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__xor__SlotFragment,
|
|
||||||
PyClass__rxor__SlotFragment,
|
|
||||||
__xor__,
|
|
||||||
__rxor__,
|
|
||||||
generate_pyclass_xor_slot,
|
|
||||||
Py_nb_xor,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__matmul__SlotFragment,
|
|
||||||
PyClass__rmatmul__SlotFragment,
|
|
||||||
__matmul__,
|
|
||||||
__rmatmul__,
|
|
||||||
generate_pyclass_matmul_slot,
|
|
||||||
Py_nb_matrix_multiply,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__truediv__SlotFragment,
|
|
||||||
PyClass__rtruediv__SlotFragment,
|
|
||||||
__truediv__,
|
|
||||||
__rtruediv__,
|
|
||||||
generate_pyclass_truediv_slot,
|
|
||||||
Py_nb_true_divide,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
define_pyclass_binary_operator_slot! {
|
|
||||||
PyClass__floordiv__SlotFragment,
|
|
||||||
PyClass__rfloordiv__SlotFragment,
|
|
||||||
__floordiv__,
|
|
||||||
__rfloordiv__,
|
|
||||||
generate_pyclass_floordiv_slot,
|
|
||||||
Py_nb_floor_divide,
|
|
||||||
binaryfunc,
|
|
||||||
}
|
|
||||||
|
|
||||||
slot_fragment_trait! {
|
|
||||||
PyClass__pow__SlotFragment,
|
|
||||||
|
|
||||||
/// # Safety: _slf and _other must be valid non-null Python objects
|
|
||||||
#[inline]
|
|
||||||
unsafe fn __pow__(
|
|
||||||
self,
|
|
||||||
_py: Python,
|
|
||||||
_slf: *mut ffi::PyObject,
|
|
||||||
_other: *mut ffi::PyObject,
|
|
||||||
_mod: *mut ffi::PyObject,
|
|
||||||
) -> PyResult<*mut ffi::PyObject> {
|
|
||||||
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
slot_fragment_trait! {
|
|
||||||
PyClass__rpow__SlotFragment,
|
|
||||||
|
|
||||||
/// # Safety: _slf and _other must be valid non-null Python objects
|
|
||||||
#[inline]
|
|
||||||
unsafe fn __rpow__(
|
|
||||||
self,
|
|
||||||
_py: Python,
|
|
||||||
_slf: *mut ffi::PyObject,
|
|
||||||
_other: *mut ffi::PyObject,
|
|
||||||
_mod: *mut ffi::PyObject,
|
|
||||||
) -> PyResult<*mut ffi::PyObject> {
|
|
||||||
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! generate_pyclass_pow_slot {
|
|
||||||
($cls:ty) => {{
|
|
||||||
unsafe extern "C" fn __wrap(
|
|
||||||
_slf: *mut $crate::ffi::PyObject,
|
|
||||||
_other: *mut $crate::ffi::PyObject,
|
|
||||||
_mod: *mut $crate::ffi::PyObject,
|
|
||||||
) -> *mut $crate::ffi::PyObject {
|
|
||||||
$crate::callback::handle_panic(|py| {
|
|
||||||
use $crate::class::impl_::*;
|
|
||||||
let collector = PyClassImplCollector::<$cls>::new();
|
|
||||||
let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
|
|
||||||
if lhs_result == $crate::ffi::Py_NotImplemented() {
|
|
||||||
$crate::ffi::Py_DECREF(lhs_result);
|
|
||||||
collector.__rpow__(py, _other, _slf, _mod)
|
|
||||||
} else {
|
|
||||||
::std::result::Result::Ok(lhs_result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
$crate::ffi::PyType_Slot {
|
|
||||||
slot: $crate::ffi::Py_nb_power,
|
|
||||||
pfunc: __wrap as $crate::ffi::ternaryfunc as _,
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
pub use generate_pyclass_pow_slot;
|
|
||||||
|
|
||||||
pub trait PyClassAllocImpl<T> {
|
|
||||||
fn alloc_impl(self) -> Option<ffi::allocfunc>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PyClassAllocImpl<T> for &'_ PyClassImplCollector<T> {
|
|
||||||
fn alloc_impl(self) -> Option<ffi::allocfunc> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PyClassFreeImpl<T> {
|
|
||||||
fn free_impl(self) -> Option<ffi::freefunc>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PyClassFreeImpl<T> for &'_ PyClassImplCollector<T> {
|
|
||||||
fn free_impl(self) -> Option<ffi::freefunc> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implements a freelist.
|
|
||||||
///
|
|
||||||
/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
|
|
||||||
/// on a Rust struct to implement it.
|
|
||||||
pub trait PyClassWithFreeList: PyClass {
|
|
||||||
fn get_free_list(py: Python) -> &mut FreeList<*mut ffi::PyObject>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of tp_alloc for `freelist` classes.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `subtype` must be a valid pointer to the type object of T or a subclass.
|
|
||||||
/// - The GIL must be held.
|
|
||||||
pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
|
|
||||||
subtype: *mut ffi::PyTypeObject,
|
|
||||||
nitems: ffi::Py_ssize_t,
|
|
||||||
) -> *mut ffi::PyObject {
|
|
||||||
let py = Python::assume_gil_acquired();
|
|
||||||
|
|
||||||
#[cfg(not(Py_3_8))]
|
|
||||||
bpo_35810_workaround(py, subtype);
|
|
||||||
|
|
||||||
let self_type = T::type_object_raw(py);
|
|
||||||
// If this type is a variable type or the subtype is not equal to this type, we cannot use the
|
|
||||||
// freelist
|
|
||||||
if nitems == 0 && subtype == self_type {
|
|
||||||
if let Some(obj) = T::get_free_list(py).pop() {
|
|
||||||
ffi::PyObject_Init(obj, subtype);
|
|
||||||
return obj as _;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ffi::PyType_GenericAlloc(subtype, nitems)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of tp_free for `freelist` classes.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `obj` must be a valid pointer to an instance of T (not a subclass).
|
|
||||||
/// - The GIL must be held.
|
|
||||||
pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
|
|
||||||
let obj = obj as *mut ffi::PyObject;
|
|
||||||
debug_assert_eq!(
|
|
||||||
T::type_object_raw(Python::assume_gil_acquired()),
|
|
||||||
ffi::Py_TYPE(obj)
|
|
||||||
);
|
|
||||||
if let Some(obj) = T::get_free_list(Python::assume_gil_acquired()).insert(obj) {
|
|
||||||
let ty = ffi::Py_TYPE(obj);
|
|
||||||
|
|
||||||
// Deduce appropriate inverse of PyType_GenericAlloc
|
|
||||||
let free = if ffi::PyType_IS_GC(ty) != 0 {
|
|
||||||
ffi::PyObject_GC_Del
|
|
||||||
} else {
|
|
||||||
ffi::PyObject_Free
|
|
||||||
};
|
|
||||||
free(obj as *mut c_void);
|
|
||||||
|
|
||||||
#[cfg(Py_3_8)]
|
|
||||||
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
|
|
||||||
ffi::Py_DECREF(ty as *mut ffi::PyObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Workaround for Python issue 35810; no longer necessary in Python 3.8
|
|
||||||
#[inline]
|
|
||||||
#[cfg(not(Py_3_8))]
|
|
||||||
unsafe fn bpo_35810_workaround(_py: Python, ty: *mut ffi::PyTypeObject) {
|
|
||||||
#[cfg(Py_LIMITED_API)]
|
|
||||||
{
|
|
||||||
// Must check version at runtime for abi3 wheels - they could run against a higher version
|
|
||||||
// than the build config suggests.
|
|
||||||
use crate::once_cell::GILOnceCell;
|
|
||||||
static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
|
|
||||||
|
|
||||||
if *IS_PYTHON_3_8.get_or_init(_py, || _py.version_info() >= (3, 8)) {
|
|
||||||
// No fix needed - the wheel is running on a sufficiently new interpreter.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ffi::Py_INCREF(ty as *mut ffi::PyObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
// General methods implementation: either dtolnay specialization trait or inventory if
|
|
||||||
// multiple-pymethods feature is enabled.
|
|
||||||
|
|
||||||
macro_rules! methods_trait {
|
|
||||||
($name:ident, $function_name: ident) => {
|
|
||||||
pub trait $name<T> {
|
|
||||||
fn $function_name(self) -> &'static [PyMethodDefType];
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> $name<T> for &'_ PyClassImplCollector<T> {
|
|
||||||
fn $function_name(self) -> &'static [PyMethodDefType] {
|
|
||||||
&[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation detail. Only to be used through our proc macro code.
|
|
||||||
/// Method storage for `#[pyclass]`.
|
|
||||||
/// Allows arbitrary `#[pymethod]` blocks to submit their methods,
|
|
||||||
/// which are eventually collected by `#[pyclass]`.
|
|
||||||
#[cfg(feature = "multiple-pymethods")]
|
|
||||||
pub trait PyClassInventory: inventory::Collect {
|
|
||||||
/// Returns the methods for a single `#[pymethods] impl` block
|
|
||||||
fn methods(&'static self) -> &'static [PyMethodDefType];
|
|
||||||
|
|
||||||
/// Returns the slots for a single `#[pymethods] impl` block
|
|
||||||
fn slots(&'static self) -> &'static [ffi::PyType_Slot];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods from #[pyo3(get, set)] on struct fields.
|
|
||||||
methods_trait!(PyClassDescriptors, py_class_descriptors);
|
|
||||||
|
|
||||||
// Methods from #[pymethods] if not using inventory.
|
|
||||||
#[cfg(not(feature = "multiple-pymethods"))]
|
|
||||||
methods_trait!(PyMethods, py_methods);
|
|
||||||
|
|
||||||
// All traits describing slots, as well as the fallback implementations for unimplemented protos
|
|
||||||
//
|
|
||||||
// Protos which are implemented use dtolnay specialization to implement for PyClassImplCollector<T>.
|
|
||||||
//
|
|
||||||
// See https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
|
|
||||||
|
|
||||||
macro_rules! slots_trait {
|
|
||||||
($name:ident, $function_name: ident) => {
|
|
||||||
pub trait $name<T> {
|
|
||||||
fn $function_name(self) -> &'static [ffi::PyType_Slot];
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> $name<T> for &'_ PyClassImplCollector<T> {
|
|
||||||
fn $function_name(self) -> &'static [ffi::PyType_Slot] {
|
|
||||||
&[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
slots_trait!(PyObjectProtocolSlots, object_protocol_slots);
|
|
||||||
slots_trait!(PyDescrProtocolSlots, descr_protocol_slots);
|
|
||||||
slots_trait!(PyGCProtocolSlots, gc_protocol_slots);
|
|
||||||
slots_trait!(PyIterProtocolSlots, iter_protocol_slots);
|
|
||||||
slots_trait!(PyMappingProtocolSlots, mapping_protocol_slots);
|
|
||||||
slots_trait!(PyNumberProtocolSlots, number_protocol_slots);
|
|
||||||
slots_trait!(PyAsyncProtocolSlots, async_protocol_slots);
|
|
||||||
slots_trait!(PySequenceProtocolSlots, sequence_protocol_slots);
|
|
||||||
slots_trait!(PyBufferProtocolSlots, buffer_protocol_slots);
|
|
||||||
|
|
||||||
// slots that PyO3 implements by default, but can be overidden by the users.
|
|
||||||
slots_trait!(PyClassDefaultSlots, py_class_default_slots);
|
|
||||||
|
|
||||||
// Protocol slots from #[pymethods] if not using inventory.
|
|
||||||
#[cfg(not(feature = "multiple-pymethods"))]
|
|
||||||
slots_trait!(PyMethodsProtocolSlots, methods_protocol_slots);
|
|
||||||
|
|
||||||
methods_trait!(PyObjectProtocolMethods, object_protocol_methods);
|
|
||||||
methods_trait!(PyAsyncProtocolMethods, async_protocol_methods);
|
|
||||||
methods_trait!(PyDescrProtocolMethods, descr_protocol_methods);
|
|
||||||
methods_trait!(PyMappingProtocolMethods, mapping_protocol_methods);
|
|
||||||
methods_trait!(PyNumberProtocolMethods, number_protocol_methods);
|
|
||||||
|
|
||||||
// Thread checkers
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait PyClassThreadChecker<T>: Sized {
|
|
||||||
fn ensure(&self);
|
|
||||||
fn new() -> Self;
|
|
||||||
private_decl! {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stub checker for `Send` types.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T: Send> PyClassThreadChecker<T> for ThreadCheckerStub<T> {
|
|
||||||
fn ensure(&self) {}
|
|
||||||
#[inline]
|
|
||||||
fn new() -> Self {
|
|
||||||
ThreadCheckerStub(PhantomData)
|
|
||||||
}
|
|
||||||
private_impl! {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PyNativeType> PyClassThreadChecker<T> for ThreadCheckerStub<crate::PyObject> {
|
|
||||||
fn ensure(&self) {}
|
|
||||||
#[inline]
|
|
||||||
fn new() -> Self {
|
|
||||||
ThreadCheckerStub(PhantomData)
|
|
||||||
}
|
|
||||||
private_impl! {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Thread checker for unsendable types.
|
|
||||||
/// Panics when the value is accessed by another thread.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct ThreadCheckerImpl<T>(thread::ThreadId, PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl<T> {
|
|
||||||
fn ensure(&self) {
|
|
||||||
if thread::current().id() != self.0 {
|
|
||||||
panic!(
|
|
||||||
"{} is unsendable, but sent to another thread!",
|
|
||||||
std::any::type_name::<T>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn new() -> Self {
|
|
||||||
ThreadCheckerImpl(thread::current().id(), PhantomData)
|
|
||||||
}
|
|
||||||
private_impl! {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Thread checker for types that have `Send` and `extends=...`.
|
|
||||||
/// Ensures that `T: Send` and the parent is not accessed by another thread.
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
|
|
||||||
|
|
||||||
impl<T: Send, U: PyClassBaseType> PyClassThreadChecker<T> for ThreadCheckerInherited<T, U> {
|
|
||||||
fn ensure(&self) {
|
|
||||||
self.1.ensure();
|
|
||||||
}
|
|
||||||
fn new() -> Self {
|
|
||||||
ThreadCheckerInherited(PhantomData, U::ThreadChecker::new())
|
|
||||||
}
|
|
||||||
private_impl! {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait denoting that this class is suitable to be used as a base type for PyClass.
|
|
||||||
pub trait PyClassBaseType: Sized {
|
|
||||||
type LayoutAsBase: PyCellLayout<Self>;
|
|
||||||
type BaseNativeType;
|
|
||||||
type ThreadChecker: PyClassThreadChecker<Self>;
|
|
||||||
type Initializer: PyObjectInit<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// All PyClasses can be used as a base type.
|
|
||||||
impl<T: PyClass> PyClassBaseType for T {
|
|
||||||
type LayoutAsBase = crate::pycell::PyCell<T>;
|
|
||||||
type BaseNativeType = T::BaseNativeType;
|
|
||||||
type ThreadChecker = T::ThreadChecker;
|
|
||||||
type Initializer = crate::pyclass_init::PyClassInitializer<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default new implementation
|
|
||||||
pub(crate) 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",
|
|
||||||
))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of tp_dealloc for all pyclasses
|
|
||||||
pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
|
|
||||||
crate::callback_body!(py, T::Layout::tp_dealloc(obj, py))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
|
|
||||||
obj: *mut ffi::PyObject,
|
|
||||||
index: ffi::Py_ssize_t,
|
|
||||||
) -> *mut ffi::PyObject {
|
|
||||||
let index = ffi::PyLong_FromSsize_t(index);
|
|
||||||
if index.is_null() {
|
|
||||||
return std::ptr::null_mut();
|
|
||||||
}
|
|
||||||
let result = ffi::PyObject_GetItem(obj, index);
|
|
||||||
ffi::Py_DECREF(index);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
|
|
||||||
obj: *mut ffi::PyObject,
|
|
||||||
index: ffi::Py_ssize_t,
|
|
||||||
value: *mut ffi::PyObject,
|
|
||||||
) -> c_int {
|
|
||||||
let index = ffi::PyLong_FromSsize_t(index);
|
|
||||||
if index.is_null() {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
let result = if value.is_null() {
|
|
||||||
ffi::PyObject_DelItem(obj, index)
|
|
||||||
} else {
|
|
||||||
ffi::PyObject_SetItem(obj, index, value)
|
|
||||||
};
|
|
||||||
ffi::Py_DECREF(index);
|
|
||||||
result
|
|
||||||
}
|
|
|
@ -4,8 +4,7 @@
|
||||||
|
|
||||||
use crate::callback::IntoPyCallbackOutput;
|
use crate::callback::IntoPyCallbackOutput;
|
||||||
use crate::derive_utils::TryFromPyCell;
|
use crate::derive_utils::TryFromPyCell;
|
||||||
use crate::err::PyResult;
|
use crate::{PyClass, PyObject};
|
||||||
use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python};
|
|
||||||
|
|
||||||
/// Python Iterator Interface.
|
/// Python Iterator Interface.
|
||||||
///
|
///
|
||||||
|
@ -73,49 +72,4 @@ pub trait PyIterNextProtocol<'p>: PyIterProtocol<'p> {
|
||||||
py_unarys_func!(iter, PyIterIterProtocol, Self::__iter__);
|
py_unarys_func!(iter, PyIterIterProtocol, Self::__iter__);
|
||||||
py_unarys_func!(iternext, PyIterNextProtocol, Self::__next__);
|
py_unarys_func!(iternext, PyIterNextProtocol, Self::__next__);
|
||||||
|
|
||||||
/// Output of `__next__` which can either `yield` the next value in the iteration, or
|
pub use crate::pyclass::{IterNextOutput, PyIterNextOutput};
|
||||||
/// `return` a value to raise `StopIteration` in Python.
|
|
||||||
///
|
|
||||||
/// See [`PyIterProtocol`](trait.PyIterProtocol.html) for an example.
|
|
||||||
pub enum IterNextOutput<T, U> {
|
|
||||||
/// The value yielded by the iterator.
|
|
||||||
Yield(T),
|
|
||||||
/// The `StopIteration` object.
|
|
||||||
Return(U),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type PyIterNextOutput = IterNextOutput<PyObject, PyObject>;
|
|
||||||
|
|
||||||
impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterNextOutput {
|
|
||||||
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
|
|
||||||
match self {
|
|
||||||
IterNextOutput::Yield(o) => Ok(o.into_ptr()),
|
|
||||||
IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::new_err((opt,))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> IntoPyCallbackOutput<PyIterNextOutput> for IterNextOutput<T, U>
|
|
||||||
where
|
|
||||||
T: IntoPy<PyObject>,
|
|
||||||
U: IntoPy<PyObject>,
|
|
||||||
{
|
|
||||||
fn convert(self, py: Python) -> PyResult<PyIterNextOutput> {
|
|
||||||
match self {
|
|
||||||
IterNextOutput::Yield(o) => Ok(IterNextOutput::Yield(o.into_py(py))),
|
|
||||||
IterNextOutput::Return(o) => Ok(IterNextOutput::Return(o.into_py(py))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> IntoPyCallbackOutput<PyIterNextOutput> for Option<T>
|
|
||||||
where
|
|
||||||
T: IntoPy<PyObject>,
|
|
||||||
{
|
|
||||||
fn convert(self, py: Python) -> PyResult<PyIterNextOutput> {
|
|
||||||
match self {
|
|
||||||
Some(o) => Ok(PyIterNextOutput::Yield(o.into_py(py))),
|
|
||||||
None => Ok(PyIterNextOutput::Return(py.None())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,220 +0,0 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
|
||||||
|
|
||||||
use crate::internal_tricks::{extract_cstr_or_leak_cstring, NulByteInString};
|
|
||||||
use crate::{ffi, PyObject, Python};
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::fmt;
|
|
||||||
use std::os::raw::c_int;
|
|
||||||
|
|
||||||
/// `PyMethodDefType` represents different types of Python callable objects.
|
|
||||||
/// It is used by the `#[pymethods]` and `#[pyproto]` annotations.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum PyMethodDefType {
|
|
||||||
/// Represents class method
|
|
||||||
Class(PyMethodDef),
|
|
||||||
/// Represents static method
|
|
||||||
Static(PyMethodDef),
|
|
||||||
/// Represents normal method
|
|
||||||
Method(PyMethodDef),
|
|
||||||
/// Represents class attribute, used by `#[attribute]`
|
|
||||||
ClassAttribute(PyClassAttributeDef),
|
|
||||||
/// Represents getter descriptor, used by `#[getter]`
|
|
||||||
Getter(PyGetterDef),
|
|
||||||
/// Represents setter descriptor, used by `#[setter]`
|
|
||||||
Setter(PySetterDef),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum PyMethodType {
|
|
||||||
PyCFunction(PyCFunction),
|
|
||||||
PyCFunctionWithKeywords(PyCFunctionWithKeywords),
|
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
|
||||||
PyCFunctionFastWithKeywords(PyCFunctionFastWithKeywords),
|
|
||||||
}
|
|
||||||
|
|
||||||
// These newtype structs serve no purpose other than wrapping which are function pointers - because
|
|
||||||
// function pointers aren't allowed in const fn, but types wrapping them are!
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct PyCFunction(pub ffi::PyCFunction);
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct PyCFunctionWithKeywords(pub ffi::PyCFunctionWithKeywords);
|
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct PyCFunctionFastWithKeywords(pub ffi::_PyCFunctionFastWithKeywords);
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct PyGetter(pub ffi::getter);
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct PySetter(pub ffi::setter);
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct PyClassAttributeFactory(pub for<'p> fn(Python<'p>) -> PyObject);
|
|
||||||
|
|
||||||
// TODO: it would be nice to use CStr in these types, but then the constructors can't be const fn
|
|
||||||
// until `CStr::from_bytes_with_nul_unchecked` is const fn.
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct PyMethodDef {
|
|
||||||
pub(crate) ml_name: &'static str,
|
|
||||||
pub(crate) ml_meth: PyMethodType,
|
|
||||||
pub(crate) ml_flags: c_int,
|
|
||||||
pub(crate) ml_doc: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct PyClassAttributeDef {
|
|
||||||
pub(crate) name: &'static str,
|
|
||||||
pub(crate) meth: PyClassAttributeFactory,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct PyGetterDef {
|
|
||||||
pub(crate) name: &'static str,
|
|
||||||
pub(crate) meth: PyGetter,
|
|
||||||
doc: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct PySetterDef {
|
|
||||||
pub(crate) name: &'static str,
|
|
||||||
pub(crate) meth: PySetter,
|
|
||||||
doc: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Sync for PyMethodDef {}
|
|
||||||
|
|
||||||
unsafe impl Sync for PyGetterDef {}
|
|
||||||
|
|
||||||
unsafe impl Sync for PySetterDef {}
|
|
||||||
|
|
||||||
impl PyMethodDef {
|
|
||||||
/// Define a function with no `*args` and `**kwargs`.
|
|
||||||
pub const fn noargs(name: &'static str, cfunction: PyCFunction, doc: &'static str) -> Self {
|
|
||||||
Self {
|
|
||||||
ml_name: name,
|
|
||||||
ml_meth: PyMethodType::PyCFunction(cfunction),
|
|
||||||
ml_flags: ffi::METH_NOARGS,
|
|
||||||
ml_doc: doc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a function that can take `*args` and `**kwargs`.
|
|
||||||
pub const fn cfunction_with_keywords(
|
|
||||||
name: &'static str,
|
|
||||||
cfunction: PyCFunctionWithKeywords,
|
|
||||||
doc: &'static str,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
ml_name: name,
|
|
||||||
ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
|
|
||||||
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
|
|
||||||
ml_doc: doc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a function that can take `*args` and `**kwargs`.
|
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
|
||||||
pub const fn fastcall_cfunction_with_keywords(
|
|
||||||
name: &'static str,
|
|
||||||
cfunction: PyCFunctionFastWithKeywords,
|
|
||||||
doc: &'static str,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
ml_name: name,
|
|
||||||
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
|
|
||||||
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
|
|
||||||
ml_doc: doc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn flags(mut self, flags: c_int) -> Self {
|
|
||||||
self.ml_flags |= flags;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert `PyMethodDef` to Python method definition struct `ffi::PyMethodDef`
|
|
||||||
pub(crate) fn as_method_def(&self) -> Result<ffi::PyMethodDef, NulByteInString> {
|
|
||||||
let meth = match self.ml_meth {
|
|
||||||
PyMethodType::PyCFunction(meth) => meth.0,
|
|
||||||
PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth.0) },
|
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
|
||||||
PyMethodType::PyCFunctionFastWithKeywords(meth) => unsafe {
|
|
||||||
std::mem::transmute(meth.0)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ffi::PyMethodDef {
|
|
||||||
ml_name: get_name(self.ml_name)?.as_ptr(),
|
|
||||||
ml_meth: Some(meth),
|
|
||||||
ml_flags: self.ml_flags,
|
|
||||||
ml_doc: get_doc(self.ml_doc)?.as_ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PyClassAttributeDef {
|
|
||||||
/// Define a class attribute.
|
|
||||||
pub const fn new(name: &'static str, meth: PyClassAttributeFactory) -> Self {
|
|
||||||
Self { name, meth }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manual implementation because `Python<'_>` does not implement `Debug` and
|
|
||||||
// trait bounds on `fn` compiler-generated derive impls are too restrictive.
|
|
||||||
impl fmt::Debug for PyClassAttributeDef {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("PyClassAttributeDef")
|
|
||||||
.field("name", &self.name)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PyGetterDef {
|
|
||||||
/// Define a getter.
|
|
||||||
pub const fn new(name: &'static str, getter: PyGetter, doc: &'static str) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
meth: getter,
|
|
||||||
doc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy descriptor information to `ffi::PyGetSetDef`
|
|
||||||
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
|
|
||||||
if dst.name.is_null() {
|
|
||||||
dst.name = get_name(self.name).unwrap().as_ptr() as _;
|
|
||||||
}
|
|
||||||
if dst.doc.is_null() {
|
|
||||||
dst.doc = get_doc(self.doc).unwrap().as_ptr() as _;
|
|
||||||
}
|
|
||||||
dst.get = Some(self.meth.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PySetterDef {
|
|
||||||
/// Define a setter.
|
|
||||||
pub const fn new(name: &'static str, setter: PySetter, doc: &'static str) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
meth: setter,
|
|
||||||
doc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy descriptor information to `ffi::PyGetSetDef`
|
|
||||||
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
|
|
||||||
if dst.name.is_null() {
|
|
||||||
dst.name = get_name(self.name).unwrap().as_ptr() as _;
|
|
||||||
}
|
|
||||||
if dst.doc.is_null() {
|
|
||||||
dst.doc = get_doc(self.doc).unwrap().as_ptr() as _;
|
|
||||||
}
|
|
||||||
dst.set = Some(self.meth.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_name(name: &'static str) -> Result<&'static CStr, NulByteInString> {
|
|
||||||
extract_cstr_or_leak_cstring(name, "Function name cannot contain NUL byte.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_doc(doc: &'static str) -> Result<&'static CStr, NulByteInString> {
|
|
||||||
extract_cstr_or_leak_cstring(doc, "Document cannot contain NUL byte.")
|
|
||||||
}
|
|
|
@ -10,12 +10,10 @@ pub mod basic;
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod descr;
|
pub mod descr;
|
||||||
pub mod gc;
|
pub mod gc;
|
||||||
#[doc(hidden)]
|
|
||||||
pub mod impl_;
|
|
||||||
pub mod iter;
|
pub mod iter;
|
||||||
pub mod mapping;
|
pub mod mapping;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod methods;
|
pub use crate::impl_::pymethods as methods;
|
||||||
pub mod number;
|
pub mod number;
|
||||||
pub mod pyasync;
|
pub mod pyasync;
|
||||||
pub mod sequence;
|
pub mod sequence;
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
|
|
||||||
use crate::callback::IntoPyCallbackOutput;
|
use crate::callback::IntoPyCallbackOutput;
|
||||||
use crate::derive_utils::TryFromPyCell;
|
use crate::derive_utils::TryFromPyCell;
|
||||||
use crate::err::PyResult;
|
use crate::{PyClass, PyObject};
|
||||||
use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python};
|
|
||||||
|
|
||||||
/// Python Async/Await support interface.
|
/// Python Async/Await support interface.
|
||||||
///
|
///
|
||||||
|
@ -58,51 +57,4 @@ py_unarys_func!(await_, PyAsyncAwaitProtocol, Self::__await__);
|
||||||
py_unarys_func!(aiter, PyAsyncAiterProtocol, Self::__aiter__);
|
py_unarys_func!(aiter, PyAsyncAiterProtocol, Self::__aiter__);
|
||||||
py_unarys_func!(anext, PyAsyncAnextProtocol, Self::__anext__);
|
py_unarys_func!(anext, PyAsyncAnextProtocol, Self::__anext__);
|
||||||
|
|
||||||
/// Output of `__anext__`.
|
pub use crate::pyclass::{IterANextOutput, PyIterANextOutput};
|
||||||
///
|
|
||||||
/// <https://docs.python.org/3/reference/expressions.html#agen.__anext__>
|
|
||||||
pub enum IterANextOutput<T, U> {
|
|
||||||
/// An expression which the generator yielded.
|
|
||||||
Yield(T),
|
|
||||||
/// A `StopAsyncIteration` object.
|
|
||||||
Return(U),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An [IterANextOutput] of Python objects.
|
|
||||||
pub type PyIterANextOutput = IterANextOutput<PyObject, PyObject>;
|
|
||||||
|
|
||||||
impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterANextOutput {
|
|
||||||
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
|
|
||||||
match self {
|
|
||||||
IterANextOutput::Yield(o) => Ok(o.into_ptr()),
|
|
||||||
IterANextOutput::Return(opt) => {
|
|
||||||
Err(crate::exceptions::PyStopAsyncIteration::new_err((opt,)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> IntoPyCallbackOutput<PyIterANextOutput> for IterANextOutput<T, U>
|
|
||||||
where
|
|
||||||
T: IntoPy<PyObject>,
|
|
||||||
U: IntoPy<PyObject>,
|
|
||||||
{
|
|
||||||
fn convert(self, py: Python) -> PyResult<PyIterANextOutput> {
|
|
||||||
match self {
|
|
||||||
IterANextOutput::Yield(o) => Ok(IterANextOutput::Yield(o.into_py(py))),
|
|
||||||
IterANextOutput::Return(o) => Ok(IterANextOutput::Return(o.into_py(py))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> IntoPyCallbackOutput<PyIterANextOutput> for Option<T>
|
|
||||||
where
|
|
||||||
T: IntoPy<PyObject>,
|
|
||||||
{
|
|
||||||
fn convert(self, py: Python) -> PyResult<PyIterANextOutput> {
|
|
||||||
match self {
|
|
||||||
Some(o) => Ok(PyIterANextOutput::Yield(o.into_py(py))),
|
|
||||||
None => Ok(PyIterANextOutput::Return(py.None())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
use crate::{ffi, PyCell, PyClass, Python};
|
use crate::{
|
||||||
|
exceptions::{PyAttributeError, PyNotImplementedError},
|
||||||
|
ffi,
|
||||||
|
impl_::freelist::FreeList,
|
||||||
|
pycell::PyCellLayout,
|
||||||
|
pyclass_init::PyObjectInit,
|
||||||
|
type_object::{PyLayout, PyTypeObject},
|
||||||
|
PyCell, PyClass, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
|
||||||
|
};
|
||||||
|
use std::{marker::PhantomData, os::raw::{c_int, c_void}, ptr::NonNull, thread};
|
||||||
|
|
||||||
/// Gets the offset of the dictionary from the start of the object in bytes.
|
/// Gets the offset of the dictionary from the start of the object in bytes.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -94,3 +103,804 @@ impl PyClassWeakRef for PyClassWeakRefSlot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This type is used as a "dummy" type on which dtolnay specializations are
|
||||||
|
/// applied to apply implementations from `#[pymethods]` & `#[pyproto]`
|
||||||
|
pub struct PyClassImplCollector<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T> PyClassImplCollector<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for PyClassImplCollector<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Clone for PyClassImplCollector<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Copy for PyClassImplCollector<T> {}
|
||||||
|
|
||||||
|
/// Implements the underlying functionality of `#[pyclass]`, assembled by various proc macros.
|
||||||
|
///
|
||||||
|
/// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail
|
||||||
|
/// and may be changed at any time.
|
||||||
|
pub trait PyClassImpl: Sized {
|
||||||
|
/// Class doc string
|
||||||
|
const DOC: &'static str = "\0";
|
||||||
|
|
||||||
|
/// #[pyclass(gc)]
|
||||||
|
const IS_GC: bool = false;
|
||||||
|
|
||||||
|
/// #[pyclass(subclass)]
|
||||||
|
const IS_BASETYPE: bool = false;
|
||||||
|
|
||||||
|
/// #[pyclass(extends=...)]
|
||||||
|
const IS_SUBCLASS: bool = false;
|
||||||
|
|
||||||
|
/// Layout
|
||||||
|
type Layout: PyLayout<Self>;
|
||||||
|
|
||||||
|
/// Base class
|
||||||
|
type BaseType: PyTypeInfo + PyTypeObject + PyClassBaseType;
|
||||||
|
|
||||||
|
/// This handles following two situations:
|
||||||
|
/// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
|
||||||
|
/// This implementation is used by default. Compile fails if `T: !Send`.
|
||||||
|
/// 2. In case `T` is `!Send`, `ThreadChecker` panics when `T` is accessed by another thread.
|
||||||
|
/// This implementation is used when `#[pyclass(unsendable)]` is given.
|
||||||
|
/// Panicking makes it safe to expose `T: !Send` to the Python interpreter, where all objects
|
||||||
|
/// can be accessed by multiple threads by `threading` module.
|
||||||
|
type ThreadChecker: PyClassThreadChecker<Self>;
|
||||||
|
|
||||||
|
#[cfg(feature = "multiple-pymethods")]
|
||||||
|
type Inventory: PyClassInventory;
|
||||||
|
|
||||||
|
fn for_each_method_def(_visitor: &mut dyn FnMut(&[PyMethodDefType])) {}
|
||||||
|
fn for_each_proto_slot(_visitor: &mut dyn FnMut(&[ffi::PyType_Slot])) {}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_new() -> Option<ffi::newfunc> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn get_alloc() -> Option<ffi::allocfunc> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn get_free() -> Option<ffi::freefunc> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn dict_offset() -> Option<ffi::Py_ssize_t> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traits describing known special methods.
|
||||||
|
|
||||||
|
pub trait PyClassNewImpl<T> {
|
||||||
|
fn new_impl(self) -> Option<ffi::newfunc>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PyClassNewImpl<T> for &'_ PyClassImplCollector<T> {
|
||||||
|
fn new_impl(self) -> Option<ffi::newfunc> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! slot_fragment_trait {
|
||||||
|
($trait_name:ident, $($default_method:tt)*) => {
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
pub trait $trait_name<T>: Sized {
|
||||||
|
$($default_method)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro which expands to three items
|
||||||
|
/// - Trait for a __setitem__ dunder
|
||||||
|
/// - Trait for the corresponding __delitem__ dunder
|
||||||
|
/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
|
||||||
|
macro_rules! define_pyclass_setattr_slot {
|
||||||
|
(
|
||||||
|
$set_trait:ident,
|
||||||
|
$del_trait:ident,
|
||||||
|
$set:ident,
|
||||||
|
$del:ident,
|
||||||
|
$set_error:expr,
|
||||||
|
$del_error:expr,
|
||||||
|
$generate_macro:ident,
|
||||||
|
$slot:ident,
|
||||||
|
$func_ty:ident,
|
||||||
|
) => {
|
||||||
|
slot_fragment_trait! {
|
||||||
|
$set_trait,
|
||||||
|
|
||||||
|
/// # Safety: _slf and _attr must be valid non-null Python objects
|
||||||
|
#[inline]
|
||||||
|
unsafe fn $set(
|
||||||
|
self,
|
||||||
|
_py: Python,
|
||||||
|
_slf: *mut ffi::PyObject,
|
||||||
|
_attr: *mut ffi::PyObject,
|
||||||
|
_value: NonNull<ffi::PyObject>,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
$set_error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slot_fragment_trait! {
|
||||||
|
$del_trait,
|
||||||
|
|
||||||
|
/// # Safety: _slf and _attr must be valid non-null Python objects
|
||||||
|
#[inline]
|
||||||
|
unsafe fn $del(
|
||||||
|
self,
|
||||||
|
_py: Python,
|
||||||
|
_slf: *mut ffi::PyObject,
|
||||||
|
_attr: *mut ffi::PyObject,
|
||||||
|
) -> PyResult<()> {
|
||||||
|
$del_error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! $generate_macro {
|
||||||
|
($cls:ty) => {{
|
||||||
|
unsafe extern "C" fn __wrap(
|
||||||
|
_slf: *mut $crate::ffi::PyObject,
|
||||||
|
attr: *mut $crate::ffi::PyObject,
|
||||||
|
value: *mut $crate::ffi::PyObject,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
use ::std::option::Option::*;
|
||||||
|
use $crate::callback::IntoPyCallbackOutput;
|
||||||
|
use $crate::impl_::pyclass::*;
|
||||||
|
$crate::callback::handle_panic(|py| {
|
||||||
|
let collector = PyClassImplCollector::<$cls>::new();
|
||||||
|
if let Some(value) = ::std::ptr::NonNull::new(value) {
|
||||||
|
collector.$set(py, _slf, attr, value).convert(py)
|
||||||
|
} else {
|
||||||
|
collector.$del(py, _slf, attr).convert(py)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$crate::ffi::PyType_Slot {
|
||||||
|
slot: $crate::ffi::$slot,
|
||||||
|
pfunc: __wrap as $crate::ffi::$func_ty as _,
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
pub use $generate_macro;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_setattr_slot! {
|
||||||
|
PyClass__setattr__SlotFragment,
|
||||||
|
PyClass__delattr__SlotFragment,
|
||||||
|
__setattr__,
|
||||||
|
__delattr__,
|
||||||
|
Err(PyAttributeError::new_err("can't set attribute")),
|
||||||
|
Err(PyAttributeError::new_err("can't delete attribute")),
|
||||||
|
generate_pyclass_setattr_slot,
|
||||||
|
Py_tp_setattro,
|
||||||
|
setattrofunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_setattr_slot! {
|
||||||
|
PyClass__set__SlotFragment,
|
||||||
|
PyClass__delete__SlotFragment,
|
||||||
|
__set__,
|
||||||
|
__delete__,
|
||||||
|
Err(PyNotImplementedError::new_err("can't set descriptor")),
|
||||||
|
Err(PyNotImplementedError::new_err("can't delete descriptor")),
|
||||||
|
generate_pyclass_setdescr_slot,
|
||||||
|
Py_tp_descr_set,
|
||||||
|
descrsetfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_setattr_slot! {
|
||||||
|
PyClass__setitem__SlotFragment,
|
||||||
|
PyClass__delitem__SlotFragment,
|
||||||
|
__setitem__,
|
||||||
|
__delitem__,
|
||||||
|
Err(PyNotImplementedError::new_err("can't set item")),
|
||||||
|
Err(PyNotImplementedError::new_err("can't delete item")),
|
||||||
|
generate_pyclass_setitem_slot,
|
||||||
|
Py_mp_ass_subscript,
|
||||||
|
objobjargproc,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Macro which expands to three items
|
||||||
|
/// - Trait for a lhs dunder e.g. __add__
|
||||||
|
/// - Trait for the corresponding rhs e.g. __radd__
|
||||||
|
/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
|
||||||
|
macro_rules! define_pyclass_binary_operator_slot {
|
||||||
|
(
|
||||||
|
$lhs_trait:ident,
|
||||||
|
$rhs_trait:ident,
|
||||||
|
$lhs:ident,
|
||||||
|
$rhs:ident,
|
||||||
|
$generate_macro:ident,
|
||||||
|
$slot:ident,
|
||||||
|
$func_ty:ident,
|
||||||
|
) => {
|
||||||
|
slot_fragment_trait! {
|
||||||
|
$lhs_trait,
|
||||||
|
|
||||||
|
/// # Safety: _slf and _other must be valid non-null Python objects
|
||||||
|
#[inline]
|
||||||
|
unsafe fn $lhs(
|
||||||
|
self,
|
||||||
|
_py: Python,
|
||||||
|
_slf: *mut ffi::PyObject,
|
||||||
|
_other: *mut ffi::PyObject,
|
||||||
|
) -> PyResult<*mut ffi::PyObject> {
|
||||||
|
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slot_fragment_trait! {
|
||||||
|
$rhs_trait,
|
||||||
|
|
||||||
|
/// # Safety: _slf and _other must be valid non-null Python objects
|
||||||
|
#[inline]
|
||||||
|
unsafe fn $rhs(
|
||||||
|
self,
|
||||||
|
_py: Python,
|
||||||
|
_slf: *mut ffi::PyObject,
|
||||||
|
_other: *mut ffi::PyObject,
|
||||||
|
) -> PyResult<*mut ffi::PyObject> {
|
||||||
|
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! $generate_macro {
|
||||||
|
($cls:ty) => {{
|
||||||
|
unsafe extern "C" fn __wrap(
|
||||||
|
_slf: *mut $crate::ffi::PyObject,
|
||||||
|
_other: *mut $crate::ffi::PyObject,
|
||||||
|
) -> *mut $crate::ffi::PyObject {
|
||||||
|
$crate::callback::handle_panic(|py| {
|
||||||
|
use $crate::impl_::pyclass::*;
|
||||||
|
let collector = PyClassImplCollector::<$cls>::new();
|
||||||
|
let lhs_result = collector.$lhs(py, _slf, _other)?;
|
||||||
|
if lhs_result == $crate::ffi::Py_NotImplemented() {
|
||||||
|
$crate::ffi::Py_DECREF(lhs_result);
|
||||||
|
collector.$rhs(py, _other, _slf)
|
||||||
|
} else {
|
||||||
|
::std::result::Result::Ok(lhs_result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$crate::ffi::PyType_Slot {
|
||||||
|
slot: $crate::ffi::$slot,
|
||||||
|
pfunc: __wrap as $crate::ffi::$func_ty as _,
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
pub use $generate_macro;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__add__SlotFragment,
|
||||||
|
PyClass__radd__SlotFragment,
|
||||||
|
__add__,
|
||||||
|
__radd__,
|
||||||
|
generate_pyclass_add_slot,
|
||||||
|
Py_nb_add,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__sub__SlotFragment,
|
||||||
|
PyClass__rsub__SlotFragment,
|
||||||
|
__sub__,
|
||||||
|
__rsub__,
|
||||||
|
generate_pyclass_sub_slot,
|
||||||
|
Py_nb_subtract,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__mul__SlotFragment,
|
||||||
|
PyClass__rmul__SlotFragment,
|
||||||
|
__mul__,
|
||||||
|
__rmul__,
|
||||||
|
generate_pyclass_mul_slot,
|
||||||
|
Py_nb_multiply,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__mod__SlotFragment,
|
||||||
|
PyClass__rmod__SlotFragment,
|
||||||
|
__mod__,
|
||||||
|
__rmod__,
|
||||||
|
generate_pyclass_mod_slot,
|
||||||
|
Py_nb_remainder,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__divmod__SlotFragment,
|
||||||
|
PyClass__rdivmod__SlotFragment,
|
||||||
|
__divmod__,
|
||||||
|
__rdivmod__,
|
||||||
|
generate_pyclass_divmod_slot,
|
||||||
|
Py_nb_divmod,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__lshift__SlotFragment,
|
||||||
|
PyClass__rlshift__SlotFragment,
|
||||||
|
__lshift__,
|
||||||
|
__rlshift__,
|
||||||
|
generate_pyclass_lshift_slot,
|
||||||
|
Py_nb_lshift,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__rshift__SlotFragment,
|
||||||
|
PyClass__rrshift__SlotFragment,
|
||||||
|
__rshift__,
|
||||||
|
__rrshift__,
|
||||||
|
generate_pyclass_rshift_slot,
|
||||||
|
Py_nb_rshift,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__and__SlotFragment,
|
||||||
|
PyClass__rand__SlotFragment,
|
||||||
|
__and__,
|
||||||
|
__rand__,
|
||||||
|
generate_pyclass_and_slot,
|
||||||
|
Py_nb_and,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__or__SlotFragment,
|
||||||
|
PyClass__ror__SlotFragment,
|
||||||
|
__or__,
|
||||||
|
__ror__,
|
||||||
|
generate_pyclass_or_slot,
|
||||||
|
Py_nb_or,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__xor__SlotFragment,
|
||||||
|
PyClass__rxor__SlotFragment,
|
||||||
|
__xor__,
|
||||||
|
__rxor__,
|
||||||
|
generate_pyclass_xor_slot,
|
||||||
|
Py_nb_xor,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__matmul__SlotFragment,
|
||||||
|
PyClass__rmatmul__SlotFragment,
|
||||||
|
__matmul__,
|
||||||
|
__rmatmul__,
|
||||||
|
generate_pyclass_matmul_slot,
|
||||||
|
Py_nb_matrix_multiply,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__truediv__SlotFragment,
|
||||||
|
PyClass__rtruediv__SlotFragment,
|
||||||
|
__truediv__,
|
||||||
|
__rtruediv__,
|
||||||
|
generate_pyclass_truediv_slot,
|
||||||
|
Py_nb_true_divide,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
define_pyclass_binary_operator_slot! {
|
||||||
|
PyClass__floordiv__SlotFragment,
|
||||||
|
PyClass__rfloordiv__SlotFragment,
|
||||||
|
__floordiv__,
|
||||||
|
__rfloordiv__,
|
||||||
|
generate_pyclass_floordiv_slot,
|
||||||
|
Py_nb_floor_divide,
|
||||||
|
binaryfunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
slot_fragment_trait! {
|
||||||
|
PyClass__pow__SlotFragment,
|
||||||
|
|
||||||
|
/// # Safety: _slf and _other must be valid non-null Python objects
|
||||||
|
#[inline]
|
||||||
|
unsafe fn __pow__(
|
||||||
|
self,
|
||||||
|
_py: Python,
|
||||||
|
_slf: *mut ffi::PyObject,
|
||||||
|
_other: *mut ffi::PyObject,
|
||||||
|
_mod: *mut ffi::PyObject,
|
||||||
|
) -> PyResult<*mut ffi::PyObject> {
|
||||||
|
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slot_fragment_trait! {
|
||||||
|
PyClass__rpow__SlotFragment,
|
||||||
|
|
||||||
|
/// # Safety: _slf and _other must be valid non-null Python objects
|
||||||
|
#[inline]
|
||||||
|
unsafe fn __rpow__(
|
||||||
|
self,
|
||||||
|
_py: Python,
|
||||||
|
_slf: *mut ffi::PyObject,
|
||||||
|
_other: *mut ffi::PyObject,
|
||||||
|
_mod: *mut ffi::PyObject,
|
||||||
|
) -> PyResult<*mut ffi::PyObject> {
|
||||||
|
Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! generate_pyclass_pow_slot {
|
||||||
|
($cls:ty) => {{
|
||||||
|
unsafe extern "C" fn __wrap(
|
||||||
|
_slf: *mut $crate::ffi::PyObject,
|
||||||
|
_other: *mut $crate::ffi::PyObject,
|
||||||
|
_mod: *mut $crate::ffi::PyObject,
|
||||||
|
) -> *mut $crate::ffi::PyObject {
|
||||||
|
$crate::callback::handle_panic(|py| {
|
||||||
|
use $crate::impl_::pyclass::*;
|
||||||
|
let collector = PyClassImplCollector::<$cls>::new();
|
||||||
|
let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
|
||||||
|
if lhs_result == $crate::ffi::Py_NotImplemented() {
|
||||||
|
$crate::ffi::Py_DECREF(lhs_result);
|
||||||
|
collector.__rpow__(py, _other, _slf, _mod)
|
||||||
|
} else {
|
||||||
|
::std::result::Result::Ok(lhs_result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$crate::ffi::PyType_Slot {
|
||||||
|
slot: $crate::ffi::Py_nb_power,
|
||||||
|
pfunc: __wrap as $crate::ffi::ternaryfunc as _,
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
pub use generate_pyclass_pow_slot;
|
||||||
|
|
||||||
|
pub trait PyClassAllocImpl<T> {
|
||||||
|
fn alloc_impl(self) -> Option<ffi::allocfunc>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PyClassAllocImpl<T> for &'_ PyClassImplCollector<T> {
|
||||||
|
fn alloc_impl(self) -> Option<ffi::allocfunc> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PyClassFreeImpl<T> {
|
||||||
|
fn free_impl(self) -> Option<ffi::freefunc>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PyClassFreeImpl<T> for &'_ PyClassImplCollector<T> {
|
||||||
|
fn free_impl(self) -> Option<ffi::freefunc> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements a freelist.
|
||||||
|
///
|
||||||
|
/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
|
||||||
|
/// on a Rust struct to implement it.
|
||||||
|
pub trait PyClassWithFreeList: PyClass {
|
||||||
|
fn get_free_list(py: Python) -> &mut FreeList<*mut ffi::PyObject>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of tp_alloc for `freelist` classes.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// - `subtype` must be a valid pointer to the type object of T or a subclass.
|
||||||
|
/// - The GIL must be held.
|
||||||
|
pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
|
||||||
|
subtype: *mut ffi::PyTypeObject,
|
||||||
|
nitems: ffi::Py_ssize_t,
|
||||||
|
) -> *mut ffi::PyObject {
|
||||||
|
let py = Python::assume_gil_acquired();
|
||||||
|
|
||||||
|
#[cfg(not(Py_3_8))]
|
||||||
|
bpo_35810_workaround(py, subtype);
|
||||||
|
|
||||||
|
let self_type = T::type_object_raw(py);
|
||||||
|
// If this type is a variable type or the subtype is not equal to this type, we cannot use the
|
||||||
|
// freelist
|
||||||
|
if nitems == 0 && subtype == self_type {
|
||||||
|
if let Some(obj) = T::get_free_list(py).pop() {
|
||||||
|
ffi::PyObject_Init(obj, subtype);
|
||||||
|
return obj as _;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ffi::PyType_GenericAlloc(subtype, nitems)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of tp_free for `freelist` classes.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// - `obj` must be a valid pointer to an instance of T (not a subclass).
|
||||||
|
/// - The GIL must be held.
|
||||||
|
pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
|
||||||
|
let obj = obj as *mut ffi::PyObject;
|
||||||
|
debug_assert_eq!(
|
||||||
|
T::type_object_raw(Python::assume_gil_acquired()),
|
||||||
|
ffi::Py_TYPE(obj)
|
||||||
|
);
|
||||||
|
if let Some(obj) = T::get_free_list(Python::assume_gil_acquired()).insert(obj) {
|
||||||
|
let ty = ffi::Py_TYPE(obj);
|
||||||
|
|
||||||
|
// Deduce appropriate inverse of PyType_GenericAlloc
|
||||||
|
let free = if ffi::PyType_IS_GC(ty) != 0 {
|
||||||
|
ffi::PyObject_GC_Del
|
||||||
|
} else {
|
||||||
|
ffi::PyObject_Free
|
||||||
|
};
|
||||||
|
free(obj as *mut c_void);
|
||||||
|
|
||||||
|
#[cfg(Py_3_8)]
|
||||||
|
if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
|
||||||
|
ffi::Py_DECREF(ty as *mut ffi::PyObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Workaround for Python issue 35810; no longer necessary in Python 3.8
|
||||||
|
#[inline]
|
||||||
|
#[cfg(not(Py_3_8))]
|
||||||
|
unsafe fn bpo_35810_workaround(_py: Python, ty: *mut ffi::PyTypeObject) {
|
||||||
|
#[cfg(Py_LIMITED_API)]
|
||||||
|
{
|
||||||
|
// Must check version at runtime for abi3 wheels - they could run against a higher version
|
||||||
|
// than the build config suggests.
|
||||||
|
use crate::once_cell::GILOnceCell;
|
||||||
|
static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
|
||||||
|
|
||||||
|
if *IS_PYTHON_3_8.get_or_init(_py, || _py.version_info() >= (3, 8)) {
|
||||||
|
// No fix needed - the wheel is running on a sufficiently new interpreter.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ffi::Py_INCREF(ty as *mut ffi::PyObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// General methods implementation: either dtolnay specialization trait or inventory if
|
||||||
|
// multiple-pymethods feature is enabled.
|
||||||
|
|
||||||
|
macro_rules! methods_trait {
|
||||||
|
($name:ident, $function_name: ident) => {
|
||||||
|
pub trait $name<T> {
|
||||||
|
fn $function_name(self) -> &'static [PyMethodDefType];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> $name<T> for &'_ PyClassImplCollector<T> {
|
||||||
|
fn $function_name(self) -> &'static [PyMethodDefType] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation detail. Only to be used through our proc macro code.
|
||||||
|
/// Method storage for `#[pyclass]`.
|
||||||
|
/// Allows arbitrary `#[pymethod]` blocks to submit their methods,
|
||||||
|
/// which are eventually collected by `#[pyclass]`.
|
||||||
|
#[cfg(feature = "multiple-pymethods")]
|
||||||
|
pub trait PyClassInventory: inventory::Collect {
|
||||||
|
/// Returns the methods for a single `#[pymethods] impl` block
|
||||||
|
fn methods(&'static self) -> &'static [PyMethodDefType];
|
||||||
|
|
||||||
|
/// Returns the slots for a single `#[pymethods] impl` block
|
||||||
|
fn slots(&'static self) -> &'static [ffi::PyType_Slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods from #[pyo3(get, set)] on struct fields.
|
||||||
|
methods_trait!(PyClassDescriptors, py_class_descriptors);
|
||||||
|
|
||||||
|
// Methods from #[pymethods] if not using inventory.
|
||||||
|
#[cfg(not(feature = "multiple-pymethods"))]
|
||||||
|
methods_trait!(PyMethods, py_methods);
|
||||||
|
|
||||||
|
// All traits describing slots, as well as the fallback implementations for unimplemented protos
|
||||||
|
//
|
||||||
|
// Protos which are implemented use dtolnay specialization to implement for PyClassImplCollector<T>.
|
||||||
|
//
|
||||||
|
// See https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md
|
||||||
|
|
||||||
|
macro_rules! slots_trait {
|
||||||
|
($name:ident, $function_name: ident) => {
|
||||||
|
pub trait $name<T> {
|
||||||
|
fn $function_name(self) -> &'static [ffi::PyType_Slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> $name<T> for &'_ PyClassImplCollector<T> {
|
||||||
|
fn $function_name(self) -> &'static [ffi::PyType_Slot] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
slots_trait!(PyObjectProtocolSlots, object_protocol_slots);
|
||||||
|
slots_trait!(PyDescrProtocolSlots, descr_protocol_slots);
|
||||||
|
slots_trait!(PyGCProtocolSlots, gc_protocol_slots);
|
||||||
|
slots_trait!(PyIterProtocolSlots, iter_protocol_slots);
|
||||||
|
slots_trait!(PyMappingProtocolSlots, mapping_protocol_slots);
|
||||||
|
slots_trait!(PyNumberProtocolSlots, number_protocol_slots);
|
||||||
|
slots_trait!(PyAsyncProtocolSlots, async_protocol_slots);
|
||||||
|
slots_trait!(PySequenceProtocolSlots, sequence_protocol_slots);
|
||||||
|
slots_trait!(PyBufferProtocolSlots, buffer_protocol_slots);
|
||||||
|
|
||||||
|
// slots that PyO3 implements by default, but can be overidden by the users.
|
||||||
|
slots_trait!(PyClassDefaultSlots, py_class_default_slots);
|
||||||
|
|
||||||
|
// Protocol slots from #[pymethods] if not using inventory.
|
||||||
|
#[cfg(not(feature = "multiple-pymethods"))]
|
||||||
|
slots_trait!(PyMethodsProtocolSlots, methods_protocol_slots);
|
||||||
|
|
||||||
|
methods_trait!(PyObjectProtocolMethods, object_protocol_methods);
|
||||||
|
methods_trait!(PyAsyncProtocolMethods, async_protocol_methods);
|
||||||
|
methods_trait!(PyDescrProtocolMethods, descr_protocol_methods);
|
||||||
|
methods_trait!(PyMappingProtocolMethods, mapping_protocol_methods);
|
||||||
|
methods_trait!(PyNumberProtocolMethods, number_protocol_methods);
|
||||||
|
|
||||||
|
// Thread checkers
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait PyClassThreadChecker<T>: Sized {
|
||||||
|
fn ensure(&self);
|
||||||
|
fn new() -> Self;
|
||||||
|
private_decl! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stub checker for `Send` types.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T: Send> PyClassThreadChecker<T> for ThreadCheckerStub<T> {
|
||||||
|
fn ensure(&self) {}
|
||||||
|
#[inline]
|
||||||
|
fn new() -> Self {
|
||||||
|
ThreadCheckerStub(PhantomData)
|
||||||
|
}
|
||||||
|
private_impl! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PyNativeType> PyClassThreadChecker<T> for ThreadCheckerStub<crate::PyObject> {
|
||||||
|
fn ensure(&self) {}
|
||||||
|
#[inline]
|
||||||
|
fn new() -> Self {
|
||||||
|
ThreadCheckerStub(PhantomData)
|
||||||
|
}
|
||||||
|
private_impl! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thread checker for unsendable types.
|
||||||
|
/// Panics when the value is accessed by another thread.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct ThreadCheckerImpl<T>(thread::ThreadId, PhantomData<T>);
|
||||||
|
|
||||||
|
impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl<T> {
|
||||||
|
fn ensure(&self) {
|
||||||
|
if thread::current().id() != self.0 {
|
||||||
|
panic!(
|
||||||
|
"{} is unsendable, but sent to another thread!",
|
||||||
|
std::any::type_name::<T>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn new() -> Self {
|
||||||
|
ThreadCheckerImpl(thread::current().id(), PhantomData)
|
||||||
|
}
|
||||||
|
private_impl! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Thread checker for types that have `Send` and `extends=...`.
|
||||||
|
/// Ensures that `T: Send` and the parent is not accessed by another thread.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
|
||||||
|
|
||||||
|
impl<T: Send, U: PyClassBaseType> PyClassThreadChecker<T> for ThreadCheckerInherited<T, U> {
|
||||||
|
fn ensure(&self) {
|
||||||
|
self.1.ensure();
|
||||||
|
}
|
||||||
|
fn new() -> Self {
|
||||||
|
ThreadCheckerInherited(PhantomData, U::ThreadChecker::new())
|
||||||
|
}
|
||||||
|
private_impl! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait denoting that this class is suitable to be used as a base type for PyClass.
|
||||||
|
pub trait PyClassBaseType: Sized {
|
||||||
|
type LayoutAsBase: PyCellLayout<Self>;
|
||||||
|
type BaseNativeType;
|
||||||
|
type ThreadChecker: PyClassThreadChecker<Self>;
|
||||||
|
type Initializer: PyObjectInit<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All PyClasses can be used as a base type.
|
||||||
|
impl<T: PyClass> PyClassBaseType for T {
|
||||||
|
type LayoutAsBase = crate::pycell::PyCell<T>;
|
||||||
|
type BaseNativeType = T::BaseNativeType;
|
||||||
|
type ThreadChecker = T::ThreadChecker;
|
||||||
|
type Initializer = crate::pyclass_init::PyClassInitializer<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default new implementation
|
||||||
|
pub(crate) 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",
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of tp_dealloc for all pyclasses
|
||||||
|
pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
|
||||||
|
crate::callback_body!(py, T::Layout::tp_dealloc(obj, py))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
|
||||||
|
obj: *mut ffi::PyObject,
|
||||||
|
index: ffi::Py_ssize_t,
|
||||||
|
) -> *mut ffi::PyObject {
|
||||||
|
let index = ffi::PyLong_FromSsize_t(index);
|
||||||
|
if index.is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
let result = ffi::PyObject_GetItem(obj, index);
|
||||||
|
ffi::Py_DECREF(index);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
|
||||||
|
obj: *mut ffi::PyObject,
|
||||||
|
index: ffi::Py_ssize_t,
|
||||||
|
value: *mut ffi::PyObject,
|
||||||
|
) -> c_int {
|
||||||
|
let index = ffi::PyLong_FromSsize_t(index);
|
||||||
|
if index.is_null() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
let result = if value.is_null() {
|
||||||
|
ffi::PyObject_DelItem(obj, index)
|
||||||
|
} else {
|
||||||
|
ffi::PyObject_SetItem(obj, index, value)
|
||||||
|
};
|
||||||
|
ffi::Py_DECREF(index);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
class::methods::PyMethodDef, derive_utils::PyFunctionArguments, types::PyCFunction, PyResult,
|
derive_utils::PyFunctionArguments, impl_::pymethods::PyMethodDef, types::PyCFunction, PyResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait PyFunctionDef {
|
pub trait PyFunctionDef {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use crate::{ffi, FromPyObject, PyAny, PyResult, Python};
|
use crate::internal_tricks::{extract_cstr_or_leak_cstring, NulByteInString};
|
||||||
|
use crate::{ffi, FromPyObject, PyAny, PyObject, PyResult, Python};
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::fmt;
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
/// Python 3.8 and up - __ipow__ has modulo argument correctly populated.
|
/// Python 3.8 and up - __ipow__ has modulo argument correctly populated.
|
||||||
#[cfg(Py_3_8)]
|
#[cfg(Py_3_8)]
|
||||||
|
@ -31,3 +35,216 @@ impl IPowModulo {
|
||||||
unsafe { py.from_borrowed_ptr::<PyAny>(ffi::Py_None()) }.extract()
|
unsafe { py.from_borrowed_ptr::<PyAny>(ffi::Py_None()) }.extract()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `PyMethodDefType` represents different types of Python callable objects.
|
||||||
|
/// It is used by the `#[pymethods]` and `#[pyproto]` annotations.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PyMethodDefType {
|
||||||
|
/// Represents class method
|
||||||
|
Class(PyMethodDef),
|
||||||
|
/// Represents static method
|
||||||
|
Static(PyMethodDef),
|
||||||
|
/// Represents normal method
|
||||||
|
Method(PyMethodDef),
|
||||||
|
/// Represents class attribute, used by `#[attribute]`
|
||||||
|
ClassAttribute(PyClassAttributeDef),
|
||||||
|
/// Represents getter descriptor, used by `#[getter]`
|
||||||
|
Getter(PyGetterDef),
|
||||||
|
/// Represents setter descriptor, used by `#[setter]`
|
||||||
|
Setter(PySetterDef),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum PyMethodType {
|
||||||
|
PyCFunction(PyCFunction),
|
||||||
|
PyCFunctionWithKeywords(PyCFunctionWithKeywords),
|
||||||
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
|
PyCFunctionFastWithKeywords(PyCFunctionFastWithKeywords),
|
||||||
|
}
|
||||||
|
|
||||||
|
// These newtype structs serve no purpose other than wrapping which are function pointers - because
|
||||||
|
// function pointers aren't allowed in const fn, but types wrapping them are!
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PyCFunction(pub ffi::PyCFunction);
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PyCFunctionWithKeywords(pub ffi::PyCFunctionWithKeywords);
|
||||||
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PyCFunctionFastWithKeywords(pub ffi::_PyCFunctionFastWithKeywords);
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PyGetter(pub ffi::getter);
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PySetter(pub ffi::setter);
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct PyClassAttributeFactory(pub for<'p> fn(Python<'p>) -> PyObject);
|
||||||
|
|
||||||
|
// TODO: it would be nice to use CStr in these types, but then the constructors can't be const fn
|
||||||
|
// until `CStr::from_bytes_with_nul_unchecked` is const fn.
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PyMethodDef {
|
||||||
|
pub(crate) ml_name: &'static str,
|
||||||
|
pub(crate) ml_meth: PyMethodType,
|
||||||
|
pub(crate) ml_flags: c_int,
|
||||||
|
pub(crate) ml_doc: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct PyClassAttributeDef {
|
||||||
|
pub(crate) name: &'static str,
|
||||||
|
pub(crate) meth: PyClassAttributeFactory,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PyGetterDef {
|
||||||
|
pub(crate) name: &'static str,
|
||||||
|
pub(crate) meth: PyGetter,
|
||||||
|
doc: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PySetterDef {
|
||||||
|
pub(crate) name: &'static str,
|
||||||
|
pub(crate) meth: PySetter,
|
||||||
|
doc: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for PyMethodDef {}
|
||||||
|
|
||||||
|
unsafe impl Sync for PyGetterDef {}
|
||||||
|
|
||||||
|
unsafe impl Sync for PySetterDef {}
|
||||||
|
|
||||||
|
impl PyMethodDef {
|
||||||
|
/// Define a function with no `*args` and `**kwargs`.
|
||||||
|
pub const fn noargs(name: &'static str, cfunction: PyCFunction, doc: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
ml_name: name,
|
||||||
|
ml_meth: PyMethodType::PyCFunction(cfunction),
|
||||||
|
ml_flags: ffi::METH_NOARGS,
|
||||||
|
ml_doc: doc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a function that can take `*args` and `**kwargs`.
|
||||||
|
pub const fn cfunction_with_keywords(
|
||||||
|
name: &'static str,
|
||||||
|
cfunction: PyCFunctionWithKeywords,
|
||||||
|
doc: &'static str,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
ml_name: name,
|
||||||
|
ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
|
||||||
|
ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
|
||||||
|
ml_doc: doc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a function that can take `*args` and `**kwargs`.
|
||||||
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
|
pub const fn fastcall_cfunction_with_keywords(
|
||||||
|
name: &'static str,
|
||||||
|
cfunction: PyCFunctionFastWithKeywords,
|
||||||
|
doc: &'static str,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
ml_name: name,
|
||||||
|
ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
|
||||||
|
ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
|
||||||
|
ml_doc: doc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn flags(mut self, flags: c_int) -> Self {
|
||||||
|
self.ml_flags |= flags;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `PyMethodDef` to Python method definition struct `ffi::PyMethodDef`
|
||||||
|
pub(crate) fn as_method_def(&self) -> Result<ffi::PyMethodDef, NulByteInString> {
|
||||||
|
let meth = match self.ml_meth {
|
||||||
|
PyMethodType::PyCFunction(meth) => meth.0,
|
||||||
|
PyMethodType::PyCFunctionWithKeywords(meth) => unsafe { std::mem::transmute(meth.0) },
|
||||||
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
|
PyMethodType::PyCFunctionFastWithKeywords(meth) => unsafe {
|
||||||
|
std::mem::transmute(meth.0)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ffi::PyMethodDef {
|
||||||
|
ml_name: get_name(self.ml_name)?.as_ptr(),
|
||||||
|
ml_meth: Some(meth),
|
||||||
|
ml_flags: self.ml_flags,
|
||||||
|
ml_doc: get_doc(self.ml_doc)?.as_ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyClassAttributeDef {
|
||||||
|
/// Define a class attribute.
|
||||||
|
pub const fn new(name: &'static str, meth: PyClassAttributeFactory) -> Self {
|
||||||
|
Self { name, meth }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual implementation because `Python<'_>` does not implement `Debug` and
|
||||||
|
// trait bounds on `fn` compiler-generated derive impls are too restrictive.
|
||||||
|
impl fmt::Debug for PyClassAttributeDef {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("PyClassAttributeDef")
|
||||||
|
.field("name", &self.name)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PyGetterDef {
|
||||||
|
/// Define a getter.
|
||||||
|
pub const fn new(name: &'static str, getter: PyGetter, doc: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
meth: getter,
|
||||||
|
doc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy descriptor information to `ffi::PyGetSetDef`
|
||||||
|
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
|
||||||
|
if dst.name.is_null() {
|
||||||
|
dst.name = get_name(self.name).unwrap().as_ptr() as _;
|
||||||
|
}
|
||||||
|
if dst.doc.is_null() {
|
||||||
|
dst.doc = get_doc(self.doc).unwrap().as_ptr() as _;
|
||||||
|
}
|
||||||
|
dst.get = Some(self.meth.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PySetterDef {
|
||||||
|
/// Define a setter.
|
||||||
|
pub const fn new(name: &'static str, setter: PySetter, doc: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
meth: setter,
|
||||||
|
doc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy descriptor information to `ffi::PyGetSetDef`
|
||||||
|
pub fn copy_to(&self, dst: &mut ffi::PyGetSetDef) {
|
||||||
|
if dst.name.is_null() {
|
||||||
|
dst.name = get_name(self.name).unwrap().as_ptr() as _;
|
||||||
|
}
|
||||||
|
if dst.doc.is_null() {
|
||||||
|
dst.doc = get_doc(self.doc).unwrap().as_ptr() as _;
|
||||||
|
}
|
||||||
|
dst.set = Some(self.meth.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(name: &'static str) -> Result<&'static CStr, NulByteInString> {
|
||||||
|
extract_cstr_or_leak_cstring(name, "Function name cannot contain NUL byte.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_doc(doc: &'static str) -> Result<&'static CStr, NulByteInString> {
|
||||||
|
extract_cstr_or_leak_cstring(doc, "Document cannot contain NUL byte.")
|
||||||
|
}
|
||||||
|
|
33
src/lib.rs
33
src/lib.rs
|
@ -300,6 +300,30 @@ pub use crate::python::{Python, PythonVersionInfo};
|
||||||
pub use crate::type_object::PyTypeInfo;
|
pub use crate::type_object::PyTypeInfo;
|
||||||
pub use crate::types::PyAny;
|
pub use crate::types::PyAny;
|
||||||
|
|
||||||
|
// Old directory layout, to be rethought?
|
||||||
|
#[cfg(not(feature = "pyproto"))]
|
||||||
|
pub mod class {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use crate::impl_::pymethods as methods;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use self::methods::{
|
||||||
|
PyClassAttributeDef, PyGetterDef, PyMethodDef, PyMethodDefType, PyMethodType, PySetterDef,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod basic {
|
||||||
|
pub use crate::pyclass::CompareOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod pyasync {
|
||||||
|
pub use crate::pyclass::{IterANextOutput, PyIterANextOutput};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod iter {
|
||||||
|
pub use crate::pyclass::{IterNextOutput, PyIterNextOutput};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use {
|
pub use {
|
||||||
|
@ -317,6 +341,7 @@ mod internal_tricks;
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod callback;
|
pub mod callback;
|
||||||
|
#[cfg(feature = "pyproto")]
|
||||||
pub mod class;
|
pub mod class;
|
||||||
pub mod conversion;
|
pub mod conversion;
|
||||||
mod conversions;
|
mod conversions;
|
||||||
|
@ -350,12 +375,16 @@ pub use crate::conversions::*;
|
||||||
)]
|
)]
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub mod proc_macro {
|
pub mod proc_macro {
|
||||||
pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, pyproto};
|
#[cfg(feature = "pyproto")]
|
||||||
|
pub use pyo3_macros::pyproto;
|
||||||
|
pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "macros", feature = "pyproto"))]
|
||||||
|
pub use pyo3_macros::pyproto;
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub use pyo3_macros::{
|
pub use pyo3_macros::{
|
||||||
pyclass, pyfunction, pymethods, pymodule, pyproto, wrap_pyfunction, wrap_pymodule, FromPyObject,
|
pyclass, pyfunction, pymethods, pymodule, wrap_pyfunction, wrap_pymodule, FromPyObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub use crate::python::Python;
|
||||||
pub use crate::types::{PyAny, PyModule};
|
pub use crate::types::{PyAny, PyModule};
|
||||||
|
|
||||||
#[cfg(feature = "macros")]
|
#[cfg(feature = "macros")]
|
||||||
pub use pyo3_macros::{
|
pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, wrap_pyfunction, FromPyObject};
|
||||||
pyclass, pyfunction, pymethods, pymodule, pyproto, wrap_pyfunction, FromPyObject,
|
|
||||||
};
|
#[cfg(all(feature = "macros", feature = "pyproto"))]
|
||||||
|
pub use pyo3_macros::pyproto;
|
||||||
|
|
|
@ -175,12 +175,11 @@
|
||||||
//! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language"
|
//! [Interior Mutability]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html "RefCell<T> and the Interior Mutability Pattern - The Rust Programming Language"
|
||||||
|
|
||||||
use crate::exceptions::PyRuntimeError;
|
use crate::exceptions::PyRuntimeError;
|
||||||
use crate::impl_::pyclass::{PyClassDict, PyClassWeakRef};
|
use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
|
||||||
use crate::pyclass::PyClass;
|
use crate::pyclass::PyClass;
|
||||||
use crate::pyclass_init::PyClassInitializer;
|
use crate::pyclass_init::PyClassInitializer;
|
||||||
use crate::type_object::{PyLayout, PySizedLayout};
|
use crate::type_object::{PyLayout, PySizedLayout};
|
||||||
use crate::types::PyAny;
|
use crate::types::PyAny;
|
||||||
use crate::{class::impl_::PyClassBaseType, class::impl_::PyClassThreadChecker};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
conversion::{AsPyPointer, FromPyPointer, ToPyObject},
|
conversion::{AsPyPointer, FromPyPointer, ToPyObject},
|
||||||
ffi::PyBaseObject_Type,
|
ffi::PyBaseObject_Type,
|
||||||
|
|
137
src/pyclass.rs
137
src/pyclass.rs
|
@ -1,12 +1,10 @@
|
||||||
//! `PyClass` and related traits.
|
//! `PyClass` and related traits.
|
||||||
use crate::{
|
use crate::{
|
||||||
class::impl_::{
|
callback::IntoPyCallbackOutput,
|
||||||
assign_sequence_item_from_mapping, fallback_new, get_sequence_item_from_mapping,
|
|
||||||
tp_dealloc, PyClassImpl,
|
|
||||||
},
|
|
||||||
ffi,
|
ffi,
|
||||||
impl_::pyclass::{PyClassDict, PyClassWeakRef},
|
impl_::pyclass::{assign_sequence_item_from_mapping, get_sequence_item_from_mapping, fallback_new, tp_dealloc, PyClassDict, PyClassImpl, PyClassWeakRef},
|
||||||
PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
|
IntoPy, IntoPyPointer, PyCell, PyErr, PyMethodDefType, PyNativeType, PyObject, PyResult,
|
||||||
|
PyTypeInfo, Python,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
|
@ -435,3 +433,130 @@ const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
|
||||||
doc: ptr::null_mut(),
|
doc: ptr::null_mut(),
|
||||||
closure: ptr::null_mut(),
|
closure: ptr::null_mut(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Operators for the `__richcmp__` method
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum CompareOp {
|
||||||
|
/// The *less than* operator.
|
||||||
|
Lt = ffi::Py_LT as isize,
|
||||||
|
/// The *less than or equal to* operator.
|
||||||
|
Le = ffi::Py_LE as isize,
|
||||||
|
/// The equality operator.
|
||||||
|
Eq = ffi::Py_EQ as isize,
|
||||||
|
/// The *not equal to* operator.
|
||||||
|
Ne = ffi::Py_NE as isize,
|
||||||
|
/// The *greater than* operator.
|
||||||
|
Gt = ffi::Py_GT as isize,
|
||||||
|
/// The *greater than or equal to* operator.
|
||||||
|
Ge = ffi::Py_GE as isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompareOp {
|
||||||
|
pub fn from_raw(op: c_int) -> Option<Self> {
|
||||||
|
match op {
|
||||||
|
ffi::Py_LT => Some(CompareOp::Lt),
|
||||||
|
ffi::Py_LE => Some(CompareOp::Le),
|
||||||
|
ffi::Py_EQ => Some(CompareOp::Eq),
|
||||||
|
ffi::Py_NE => Some(CompareOp::Ne),
|
||||||
|
ffi::Py_GT => Some(CompareOp::Gt),
|
||||||
|
ffi::Py_GE => Some(CompareOp::Ge),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output of `__next__` which can either `yield` the next value in the iteration, or
|
||||||
|
/// `return` a value to raise `StopIteration` in Python.
|
||||||
|
///
|
||||||
|
/// See [`PyIterProtocol`](trait.PyIterProtocol.html) for an example.
|
||||||
|
pub enum IterNextOutput<T, U> {
|
||||||
|
/// The value yielded by the iterator.
|
||||||
|
Yield(T),
|
||||||
|
/// The `StopIteration` object.
|
||||||
|
Return(U),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PyIterNextOutput = IterNextOutput<PyObject, PyObject>;
|
||||||
|
|
||||||
|
impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterNextOutput {
|
||||||
|
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
|
||||||
|
match self {
|
||||||
|
IterNextOutput::Yield(o) => Ok(o.into_ptr()),
|
||||||
|
IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::new_err((opt,))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> IntoPyCallbackOutput<PyIterNextOutput> for IterNextOutput<T, U>
|
||||||
|
where
|
||||||
|
T: IntoPy<PyObject>,
|
||||||
|
U: IntoPy<PyObject>,
|
||||||
|
{
|
||||||
|
fn convert(self, py: Python) -> PyResult<PyIterNextOutput> {
|
||||||
|
match self {
|
||||||
|
IterNextOutput::Yield(o) => Ok(IterNextOutput::Yield(o.into_py(py))),
|
||||||
|
IterNextOutput::Return(o) => Ok(IterNextOutput::Return(o.into_py(py))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoPyCallbackOutput<PyIterNextOutput> for Option<T>
|
||||||
|
where
|
||||||
|
T: IntoPy<PyObject>,
|
||||||
|
{
|
||||||
|
fn convert(self, py: Python) -> PyResult<PyIterNextOutput> {
|
||||||
|
match self {
|
||||||
|
Some(o) => Ok(PyIterNextOutput::Yield(o.into_py(py))),
|
||||||
|
None => Ok(PyIterNextOutput::Return(py.None())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output of `__anext__`.
|
||||||
|
///
|
||||||
|
/// <https://docs.python.org/3/reference/expressions.html#agen.__anext__>
|
||||||
|
pub enum IterANextOutput<T, U> {
|
||||||
|
/// An expression which the generator yielded.
|
||||||
|
Yield(T),
|
||||||
|
/// A `StopAsyncIteration` object.
|
||||||
|
Return(U),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [IterANextOutput] of Python objects.
|
||||||
|
pub type PyIterANextOutput = IterANextOutput<PyObject, PyObject>;
|
||||||
|
|
||||||
|
impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterANextOutput {
|
||||||
|
fn convert(self, _py: Python) -> PyResult<*mut ffi::PyObject> {
|
||||||
|
match self {
|
||||||
|
IterANextOutput::Yield(o) => Ok(o.into_ptr()),
|
||||||
|
IterANextOutput::Return(opt) => {
|
||||||
|
Err(crate::exceptions::PyStopAsyncIteration::new_err((opt,)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> IntoPyCallbackOutput<PyIterANextOutput> for IterANextOutput<T, U>
|
||||||
|
where
|
||||||
|
T: IntoPy<PyObject>,
|
||||||
|
U: IntoPy<PyObject>,
|
||||||
|
{
|
||||||
|
fn convert(self, py: Python) -> PyResult<PyIterANextOutput> {
|
||||||
|
match self {
|
||||||
|
IterANextOutput::Yield(o) => Ok(IterANextOutput::Yield(o.into_py(py))),
|
||||||
|
IterANextOutput::Return(o) => Ok(IterANextOutput::Return(o.into_py(py))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoPyCallbackOutput<PyIterANextOutput> for Option<T>
|
||||||
|
where
|
||||||
|
T: IntoPy<PyObject>,
|
||||||
|
{
|
||||||
|
fn convert(self, py: Python) -> PyResult<PyIterANextOutput> {
|
||||||
|
match self {
|
||||||
|
Some(o) => Ok(PyIterANextOutput::Yield(o.into_py(py))),
|
||||||
|
None => Ok(PyIterANextOutput::Return(py.None())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! Contains initialization utilities for `#[pyclass]`.
|
//! Contains initialization utilities for `#[pyclass]`.
|
||||||
use crate::class::impl_::PyClassThreadChecker;
|
use crate::callback::IntoPyCallbackOutput;
|
||||||
use crate::impl_::pyclass::{PyClassDict, PyClassWeakRef};
|
use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
|
||||||
use crate::{callback::IntoPyCallbackOutput, class::impl_::PyClassBaseType};
|
|
||||||
use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python};
|
use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python};
|
||||||
use crate::{
|
use crate::{
|
||||||
ffi::PyTypeObject,
|
ffi::PyTypeObject,
|
||||||
|
|
|
@ -2,8 +2,9 @@ use crate::derive_utils::PyFunctionArguments;
|
||||||
use crate::exceptions::PyValueError;
|
use crate::exceptions::PyValueError;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
class::methods::{self, PyMethodDef},
|
ffi,
|
||||||
ffi, types, AsPyPointer,
|
impl_::pymethods::{self, PyMethodDef},
|
||||||
|
types, AsPyPointer,
|
||||||
};
|
};
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
|
|
||||||
|
@ -64,7 +65,11 @@ impl PyCFunction {
|
||||||
py_or_module: PyFunctionArguments<'a>,
|
py_or_module: PyFunctionArguments<'a>,
|
||||||
) -> PyResult<&'a Self> {
|
) -> PyResult<&'a Self> {
|
||||||
Self::internal_new(
|
Self::internal_new(
|
||||||
PyMethodDef::cfunction_with_keywords(name, methods::PyCFunctionWithKeywords(fun), doc),
|
PyMethodDef::cfunction_with_keywords(
|
||||||
|
name,
|
||||||
|
pymethods::PyCFunctionWithKeywords(fun),
|
||||||
|
doc,
|
||||||
|
),
|
||||||
py_or_module,
|
py_or_module,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +82,7 @@ impl PyCFunction {
|
||||||
py_or_module: PyFunctionArguments<'a>,
|
py_or_module: PyFunctionArguments<'a>,
|
||||||
) -> PyResult<&'a Self> {
|
) -> PyResult<&'a Self> {
|
||||||
Self::internal_new(
|
Self::internal_new(
|
||||||
PyMethodDef::noargs(name, methods::PyCFunction(fun), doc),
|
PyMethodDef::noargs(name, pymethods::PyCFunction(fun), doc),
|
||||||
py_or_module,
|
py_or_module,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -115,9 +120,9 @@ impl PyCFunction {
|
||||||
),
|
),
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
let method_def = methods::PyMethodDef::cfunction_with_keywords(
|
let method_def = pymethods::PyMethodDef::cfunction_with_keywords(
|
||||||
"pyo3-closure",
|
"pyo3-closure",
|
||||||
methods::PyCFunctionWithKeywords(run_closure::<F, R>),
|
pymethods::PyCFunctionWithKeywords(run_closure::<F, R>),
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
Self::internal_new_from_pointers(method_def, py, capsule.as_ptr(), std::ptr::null_mut())
|
Self::internal_new_from_pointers(method_def, py, capsule.as_ptr(), std::ptr::null_mut())
|
||||||
|
|
|
@ -196,10 +196,10 @@ macro_rules! pyobject_native_type_sized {
|
||||||
($name:ty, $layout:path $(;$generics:ident)*) => {
|
($name:ty, $layout:path $(;$generics:ident)*) => {
|
||||||
unsafe impl $crate::type_object::PyLayout<$name> for $layout {}
|
unsafe impl $crate::type_object::PyLayout<$name> for $layout {}
|
||||||
impl $crate::type_object::PySizedLayout<$name> for $layout {}
|
impl $crate::type_object::PySizedLayout<$name> for $layout {}
|
||||||
impl<'a, $($generics,)*> $crate::class::impl_::PyClassBaseType for $name {
|
impl<'a, $($generics,)*> $crate::impl_::pyclass::PyClassBaseType for $name {
|
||||||
type LayoutAsBase = $crate::pycell::PyCellBase<$layout>;
|
type LayoutAsBase = $crate::pycell::PyCellBase<$layout>;
|
||||||
type BaseNativeType = $name;
|
type BaseNativeType = $name;
|
||||||
type ThreadChecker = $crate::class::impl_::ThreadCheckerStub<$crate::PyObject>;
|
type ThreadChecker = $crate::impl_::pyclass::ThreadCheckerStub<$crate::PyObject>;
|
||||||
type Initializer = $crate::pyclass_init::PyNativeTypeInitializer<Self>;
|
type Initializer = $crate::pyclass_init::PyNativeTypeInitializer<Self>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(deprecated)] // for deprecated protocol methods
|
#![allow(deprecated)] // for deprecated protocol methods
|
||||||
#![cfg(feature = "macros")]
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")]
|
||||||
|
|
||||||
use pyo3::class::basic::CompareOp;
|
use pyo3::class::basic::CompareOp;
|
||||||
use pyo3::class::*;
|
use pyo3::class::*;
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
#![cfg(feature = "macros")]
|
#![cfg(feature = "macros")]
|
||||||
#![cfg(not(Py_LIMITED_API))]
|
#![cfg(not(Py_LIMITED_API))]
|
||||||
|
|
||||||
use pyo3::{
|
use pyo3::{buffer::PyBuffer, exceptions::PyBufferError, ffi, prelude::*, AsPyPointer};
|
||||||
buffer::PyBuffer, class::PyBufferProtocol, exceptions::PyBufferError, ffi, prelude::*,
|
|
||||||
AsPyPointer,
|
|
||||||
};
|
|
||||||
use std::{
|
use std::{
|
||||||
ffi::CStr,
|
ffi::CStr,
|
||||||
os::raw::{c_int, c_void},
|
os::raw::{c_int, c_void},
|
||||||
|
@ -28,9 +25,13 @@ struct TestBufferErrors {
|
||||||
error: Option<TestGetBufferError>,
|
error: Option<TestGetBufferError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyproto]
|
#[pymethods]
|
||||||
impl PyBufferProtocol for TestBufferErrors {
|
impl TestBufferErrors {
|
||||||
fn bf_getbuffer(slf: PyRefMut<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> {
|
unsafe fn __getbuffer__(
|
||||||
|
slf: PyRefMut<Self>,
|
||||||
|
view: *mut ffi::Py_buffer,
|
||||||
|
flags: c_int,
|
||||||
|
) -> PyResult<()> {
|
||||||
if view.is_null() {
|
if view.is_null() {
|
||||||
return Err(PyBufferError::new_err("View is null"));
|
return Err(PyBufferError::new_err("View is null"));
|
||||||
}
|
}
|
||||||
|
@ -39,13 +40,10 @@ impl PyBufferProtocol for TestBufferErrors {
|
||||||
return Err(PyBufferError::new_err("Object is not writable"));
|
return Err(PyBufferError::new_err("Object is not writable"));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
|
||||||
(*view).obj = ffi::_Py_NewRef(slf.as_ptr());
|
(*view).obj = ffi::_Py_NewRef(slf.as_ptr());
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = &slf.buf;
|
let bytes = &slf.buf;
|
||||||
|
|
||||||
unsafe {
|
|
||||||
(*view).buf = bytes.as_ptr() as *mut c_void;
|
(*view).buf = bytes.as_ptr() as *mut c_void;
|
||||||
(*view).len = bytes.len() as isize;
|
(*view).len = bytes.len() as isize;
|
||||||
(*view).readonly = 1;
|
(*view).readonly = 1;
|
||||||
|
@ -80,12 +78,9 @@ impl PyBufferProtocol for TestBufferErrors {
|
||||||
IncorrectAlignment => (*view).buf = (*view).buf.add(1),
|
IncorrectAlignment => (*view).buf = (*view).buf.add(1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bf_releasebuffer(_slf: PyRefMut<Self>, _view: *mut ffi::Py_buffer) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![cfg(feature = "macros")]
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")]
|
||||||
#![cfg(not(Py_LIMITED_API))]
|
#![cfg(not(Py_LIMITED_API))]
|
||||||
|
|
||||||
use pyo3::buffer::PyBuffer;
|
use pyo3::buffer::PyBuffer;
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
use pyo3::exceptions::PyValueError;
|
use pyo3::exceptions::PyValueError;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyDict, PyString, PyTuple};
|
use pyo3::types::{PyDict, PyString, PyTuple};
|
||||||
use pyo3::PyMappingProtocol;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod common;
|
mod common;
|
||||||
|
@ -39,8 +38,8 @@ pub struct PyA {
|
||||||
foo: Option<String>,
|
foo: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyproto]
|
#[pymethods]
|
||||||
impl PyMappingProtocol for PyA {
|
impl PyA {
|
||||||
fn __getitem__(&self, key: String) -> pyo3::PyResult<String> {
|
fn __getitem__(&self, key: String) -> pyo3::PyResult<String> {
|
||||||
if key == "t" {
|
if key == "t" {
|
||||||
Ok("bar".into())
|
Ok("bar".into())
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![cfg(feature = "macros")]
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")] // FIXME: #[pymethods] to support gc protocol
|
||||||
|
|
||||||
use pyo3::class::PyGCProtocol;
|
use pyo3::class::PyGCProtocol;
|
||||||
use pyo3::class::PyTraverseError;
|
use pyo3::class::PyTraverseError;
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")]
|
||||||
|
|
||||||
|
use pyo3::class::PyGCProtocol;
|
||||||
|
use pyo3::class::PyTraverseError;
|
||||||
|
use pyo3::class::PyVisit;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::type_object::PyTypeObject;
|
||||||
|
use pyo3::{py_run, AsPyPointer, PyCell, PyTryInto};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[pyclass(freelist = 2)]
|
||||||
|
struct ClassWithFreelist {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn class_with_freelist() {
|
||||||
|
let ptr;
|
||||||
|
{
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let inst = Py::new(py, ClassWithFreelist {}).unwrap();
|
||||||
|
let _inst2 = Py::new(py, ClassWithFreelist {}).unwrap();
|
||||||
|
ptr = inst.as_ptr();
|
||||||
|
drop(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let inst3 = Py::new(py, ClassWithFreelist {}).unwrap();
|
||||||
|
assert_eq!(ptr, inst3.as_ptr());
|
||||||
|
|
||||||
|
let inst4 = Py::new(py, ClassWithFreelist {}).unwrap();
|
||||||
|
assert_ne!(ptr, inst4.as_ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestDropCall {
|
||||||
|
drop_called: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TestDropCall {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.drop_called.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[pyclass]
|
||||||
|
struct DataIsDropped {
|
||||||
|
member1: TestDropCall,
|
||||||
|
member2: TestDropCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn data_is_dropped() {
|
||||||
|
let drop_called1 = Arc::new(AtomicBool::new(false));
|
||||||
|
let drop_called2 = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
{
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let data_is_dropped = DataIsDropped {
|
||||||
|
member1: TestDropCall {
|
||||||
|
drop_called: Arc::clone(&drop_called1),
|
||||||
|
},
|
||||||
|
member2: TestDropCall {
|
||||||
|
drop_called: Arc::clone(&drop_called2),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let inst = Py::new(py, data_is_dropped).unwrap();
|
||||||
|
assert!(!drop_called1.load(Ordering::Relaxed));
|
||||||
|
assert!(!drop_called2.load(Ordering::Relaxed));
|
||||||
|
drop(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(drop_called1.load(Ordering::Relaxed));
|
||||||
|
assert!(drop_called2.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[pyclass]
|
||||||
|
struct GcIntegration {
|
||||||
|
self_ref: PyObject,
|
||||||
|
dropped: TestDropCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyproto]
|
||||||
|
impl PyGCProtocol for GcIntegration {
|
||||||
|
fn __traverse__(&self, visit: PyVisit) -> Result<(), PyTraverseError> {
|
||||||
|
visit.call(&self.self_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __clear__(&mut self) {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
self.self_ref = gil.python().None();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gc_integration() {
|
||||||
|
let drop_called = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
{
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let inst = PyCell::new(
|
||||||
|
py,
|
||||||
|
GcIntegration {
|
||||||
|
self_ref: py.None(),
|
||||||
|
dropped: TestDropCall {
|
||||||
|
drop_called: Arc::clone(&drop_called),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut borrow = inst.borrow_mut();
|
||||||
|
borrow.self_ref = inst.to_object(py);
|
||||||
|
}
|
||||||
|
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
py.run("import gc; gc.collect()", None, None).unwrap();
|
||||||
|
assert!(drop_called.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(gc)]
|
||||||
|
struct GcIntegration2 {}
|
||||||
|
|
||||||
|
#[pyproto]
|
||||||
|
impl PyGCProtocol for GcIntegration2 {
|
||||||
|
fn __traverse__(&self, _visit: PyVisit) -> Result<(), PyTraverseError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn __clear__(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gc_integration2() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let inst = PyCell::new(py, GcIntegration2 {}).unwrap();
|
||||||
|
py_run!(py, inst, "import gc; assert inst in gc.get_objects()");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(subclass)]
|
||||||
|
struct BaseClassWithDrop {
|
||||||
|
data: Option<Arc<AtomicBool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl BaseClassWithDrop {
|
||||||
|
#[new]
|
||||||
|
fn new() -> BaseClassWithDrop {
|
||||||
|
BaseClassWithDrop { data: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for BaseClassWithDrop {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(data) = &self.data {
|
||||||
|
data.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(extends = BaseClassWithDrop)]
|
||||||
|
struct SubClassWithDrop {
|
||||||
|
data: Option<Arc<AtomicBool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl SubClassWithDrop {
|
||||||
|
#[new]
|
||||||
|
fn new() -> (Self, BaseClassWithDrop) {
|
||||||
|
(
|
||||||
|
SubClassWithDrop { data: None },
|
||||||
|
BaseClassWithDrop { data: None },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SubClassWithDrop {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(data) = &self.data {
|
||||||
|
data.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inheritance_with_new_methods_with_drop() {
|
||||||
|
let drop_called1 = Arc::new(AtomicBool::new(false));
|
||||||
|
let drop_called2 = Arc::new(AtomicBool::new(false));
|
||||||
|
|
||||||
|
{
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let _typebase = py.get_type::<BaseClassWithDrop>();
|
||||||
|
let typeobj = py.get_type::<SubClassWithDrop>();
|
||||||
|
let inst = typeobj.call((), None).unwrap();
|
||||||
|
|
||||||
|
let obj: &PyCell<SubClassWithDrop> = inst.try_into().unwrap();
|
||||||
|
let mut obj_ref_mut = obj.borrow_mut();
|
||||||
|
obj_ref_mut.data = Some(Arc::clone(&drop_called1));
|
||||||
|
let base: &mut BaseClassWithDrop = obj_ref_mut.as_mut();
|
||||||
|
base.data = Some(Arc::clone(&drop_called2));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(drop_called1.load(Ordering::Relaxed));
|
||||||
|
assert!(drop_called2.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(gc)]
|
||||||
|
struct TraversableClass {
|
||||||
|
traversed: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TraversableClass {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
traversed: AtomicBool::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyproto]
|
||||||
|
impl PyGCProtocol for TraversableClass {
|
||||||
|
fn __clear__(&mut self) {}
|
||||||
|
fn __traverse__(&self, _visit: PyVisit) -> Result<(), PyTraverseError> {
|
||||||
|
self.traversed.store(true, Ordering::Relaxed);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_type_traverse(tp: *mut pyo3::ffi::PyTypeObject) -> Option<pyo3::ffi::traverseproc> {
|
||||||
|
std::mem::transmute(pyo3::ffi::PyType_GetSlot(tp, pyo3::ffi::Py_tp_traverse))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gc_during_borrow() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// declare a dummy visitor function
|
||||||
|
extern "C" fn novisit(
|
||||||
|
_object: *mut pyo3::ffi::PyObject,
|
||||||
|
_arg: *mut core::ffi::c_void,
|
||||||
|
) -> std::os::raw::c_int {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the traverse function
|
||||||
|
let ty = TraversableClass::type_object(py).as_type_ptr();
|
||||||
|
let traverse = get_type_traverse(ty).unwrap();
|
||||||
|
|
||||||
|
// create an object and check that traversing it works normally
|
||||||
|
// when it's not borrowed
|
||||||
|
let cell = PyCell::new(py, TraversableClass::new()).unwrap();
|
||||||
|
let obj = cell.to_object(py);
|
||||||
|
assert!(!cell.borrow().traversed.load(Ordering::Relaxed));
|
||||||
|
traverse(obj.as_ptr(), novisit, std::ptr::null_mut());
|
||||||
|
assert!(cell.borrow().traversed.load(Ordering::Relaxed));
|
||||||
|
|
||||||
|
// create an object and check that it is not traversed if the GC
|
||||||
|
// is invoked while it is already borrowed mutably
|
||||||
|
let cell2 = PyCell::new(py, TraversableClass::new()).unwrap();
|
||||||
|
let obj2 = cell2.to_object(py);
|
||||||
|
let guard = cell2.borrow_mut();
|
||||||
|
assert!(!guard.traversed.load(Ordering::Relaxed));
|
||||||
|
traverse(obj2.as_ptr(), novisit, std::ptr::null_mut());
|
||||||
|
assert!(!guard.traversed.load(Ordering::Relaxed));
|
||||||
|
drop(guard);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ use pyo3::prelude::*;
|
||||||
use pyo3::py_run;
|
use pyo3::py_run;
|
||||||
use pyo3::types::IntoPyDict;
|
use pyo3::types::IntoPyDict;
|
||||||
use pyo3::types::PyList;
|
use pyo3::types::PyList;
|
||||||
use pyo3::PyMappingProtocol;
|
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
@ -33,10 +32,7 @@ impl Mapping {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[pyproto]
|
|
||||||
impl PyMappingProtocol for Mapping {
|
|
||||||
fn __len__(&self) -> usize {
|
fn __len__(&self) -> usize {
|
||||||
self.index.len()
|
self.index.len()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")]
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use pyo3::exceptions::PyKeyError;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::py_run;
|
||||||
|
use pyo3::types::IntoPyDict;
|
||||||
|
use pyo3::types::PyList;
|
||||||
|
use pyo3::PyMappingProtocol;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct Mapping {
|
||||||
|
index: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Mapping {
|
||||||
|
#[new]
|
||||||
|
fn new(elements: Option<&PyList>) -> PyResult<Self> {
|
||||||
|
if let Some(pylist) = elements {
|
||||||
|
let mut elems = HashMap::with_capacity(pylist.len());
|
||||||
|
for (i, pyelem) in pylist.into_iter().enumerate() {
|
||||||
|
let elem = String::extract(pyelem)?;
|
||||||
|
elems.insert(elem, i);
|
||||||
|
}
|
||||||
|
Ok(Self { index: elems })
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
index: HashMap::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyproto]
|
||||||
|
impl PyMappingProtocol for Mapping {
|
||||||
|
fn __len__(&self) -> usize {
|
||||||
|
self.index.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __getitem__(&self, query: String) -> PyResult<usize> {
|
||||||
|
self.index
|
||||||
|
.get(&query)
|
||||||
|
.copied()
|
||||||
|
.ok_or_else(|| PyKeyError::new_err("unknown key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __setitem__(&mut self, key: String, value: usize) {
|
||||||
|
self.index.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __delitem__(&mut self, key: String) -> PyResult<()> {
|
||||||
|
if self.index.remove(&key).is_none() {
|
||||||
|
Err(PyKeyError::new_err("unknown key"))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a dict with `m = Mapping(['1', '2', '3'])`.
|
||||||
|
fn map_dict(py: Python) -> &pyo3::types::PyDict {
|
||||||
|
let d = [("Mapping", py.get_type::<Mapping>())].into_py_dict(py);
|
||||||
|
py_run!(py, *d, "m = Mapping(['1', '2', '3'])");
|
||||||
|
d
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_getitem() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let d = map_dict(py);
|
||||||
|
|
||||||
|
py_assert!(py, *d, "m['1'] == 0");
|
||||||
|
py_assert!(py, *d, "m['2'] == 1");
|
||||||
|
py_assert!(py, *d, "m['3'] == 2");
|
||||||
|
py_expect_exception!(py, *d, "print(m['4'])", PyKeyError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setitem() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let d = map_dict(py);
|
||||||
|
|
||||||
|
py_run!(py, *d, "m['1'] = 4; assert m['1'] == 4");
|
||||||
|
py_run!(py, *d, "m['0'] = 0; assert m['0'] == 0");
|
||||||
|
py_assert!(py, *d, "len(m) == 4");
|
||||||
|
py_expect_exception!(py, *d, "m[0] = 'hello'", PyTypeError);
|
||||||
|
py_expect_exception!(py, *d, "m[0] = -1", PyTypeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delitem() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = map_dict(py);
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"del m['1']; assert len(m) == 2 and m['2'] == 1 and m['3'] == 2"
|
||||||
|
);
|
||||||
|
py_expect_exception!(py, *d, "del m[-1]", PyTypeError);
|
||||||
|
py_expect_exception!(py, *d, "del m['4']", PyKeyError);
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
#![cfg(feature = "macros")]
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")]
|
||||||
|
|
||||||
use pyo3::class::{
|
use pyo3::class::{
|
||||||
PyAsyncProtocol, PyDescrProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol,
|
PyAsyncProtocol, PyDescrProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! Test slf: PyRef/PyMutRef<Self>(especially, slf.into::<Py>) works
|
//! Test slf: PyRef/PyMutRef<Self>(especially, slf.into::<Py>) works
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyBytes, PyString};
|
use pyo3::types::{PyBytes, PyString};
|
||||||
use pyo3::{PyCell, PyIterProtocol};
|
use pyo3::PyCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
@ -54,9 +54,9 @@ struct Iter {
|
||||||
idx: usize,
|
idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyproto]
|
#[pymethods]
|
||||||
impl PyIterProtocol for Iter {
|
impl Iter {
|
||||||
fn __iter__(slf: PyRef<Self>) -> PyRef<'p, Self> {
|
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
|
||||||
slf
|
slf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![cfg(feature = "macros")]
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")] // FIXME: change this to use #[pymethods] once supports sequence protocol
|
||||||
|
|
||||||
use pyo3::class::PySequenceProtocol;
|
use pyo3::class::PySequenceProtocol;
|
||||||
use pyo3::exceptions::{PyIndexError, PyValueError};
|
use pyo3::exceptions::{PyIndexError, PyValueError};
|
||||||
|
|
|
@ -0,0 +1,304 @@
|
||||||
|
#![cfg(feature = "macros")]
|
||||||
|
#![cfg(feature = "pyproto")]
|
||||||
|
|
||||||
|
use pyo3::class::PySequenceProtocol;
|
||||||
|
use pyo3::exceptions::{PyIndexError, PyValueError};
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::{IntoPyDict, PyList};
|
||||||
|
|
||||||
|
use pyo3::py_run;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct ByteSequence {
|
||||||
|
elements: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl ByteSequence {
|
||||||
|
#[new]
|
||||||
|
fn new(elements: Option<&PyList>) -> PyResult<Self> {
|
||||||
|
if let Some(pylist) = elements {
|
||||||
|
let mut elems = Vec::with_capacity(pylist.len());
|
||||||
|
for pyelem in pylist.into_iter() {
|
||||||
|
let elem = u8::extract(pyelem)?;
|
||||||
|
elems.push(elem);
|
||||||
|
}
|
||||||
|
Ok(Self { elements: elems })
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
elements: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyproto]
|
||||||
|
impl PySequenceProtocol for ByteSequence {
|
||||||
|
fn __len__(&self) -> usize {
|
||||||
|
self.elements.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __getitem__(&self, idx: isize) -> PyResult<u8> {
|
||||||
|
self.elements
|
||||||
|
.get(idx as usize)
|
||||||
|
.copied()
|
||||||
|
.ok_or_else(|| PyIndexError::new_err("list index out of range"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __setitem__(&mut self, idx: isize, value: u8) {
|
||||||
|
self.elements[idx as usize] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __delitem__(&mut self, idx: isize) -> PyResult<()> {
|
||||||
|
if (idx < self.elements.len() as isize) && (idx >= 0) {
|
||||||
|
self.elements.remove(idx as usize);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(PyIndexError::new_err("list index out of range"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __contains__(&self, other: &PyAny) -> bool {
|
||||||
|
match u8::extract(other) {
|
||||||
|
Ok(x) => self.elements.contains(&x),
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __concat__(&self, other: PyRef<'p, Self>) -> Self {
|
||||||
|
let mut elements = self.elements.clone();
|
||||||
|
elements.extend_from_slice(&other.elements);
|
||||||
|
Self { elements }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __repeat__(&self, count: isize) -> PyResult<Self> {
|
||||||
|
if count >= 0 {
|
||||||
|
let mut elements = Vec::with_capacity(self.elements.len() * count as usize);
|
||||||
|
for _ in 0..count {
|
||||||
|
elements.extend(&self.elements);
|
||||||
|
}
|
||||||
|
Ok(Self { elements })
|
||||||
|
} else {
|
||||||
|
Err(PyValueError::new_err("invalid repeat count"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a dict with `s = ByteSequence([1, 2, 3])`.
|
||||||
|
fn seq_dict(py: Python) -> &pyo3::types::PyDict {
|
||||||
|
let d = [("ByteSequence", py.get_type::<ByteSequence>())].into_py_dict(py);
|
||||||
|
// Though we can construct `s` in Rust, let's test `__new__` works.
|
||||||
|
py_run!(py, *d, "s = ByteSequence([1, 2, 3])");
|
||||||
|
d
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_getitem() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let d = seq_dict(py);
|
||||||
|
|
||||||
|
py_assert!(py, *d, "s[0] == 1");
|
||||||
|
py_assert!(py, *d, "s[1] == 2");
|
||||||
|
py_assert!(py, *d, "s[2] == 3");
|
||||||
|
py_expect_exception!(py, *d, "print(s[-4])", PyIndexError);
|
||||||
|
py_expect_exception!(py, *d, "print(s[4])", PyIndexError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setitem() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
let d = seq_dict(py);
|
||||||
|
|
||||||
|
py_run!(py, *d, "s[0] = 4; assert list(s) == [4, 2, 3]");
|
||||||
|
py_expect_exception!(py, *d, "s[0] = 'hello'", PyTypeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delitem() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = [("ByteSequence", py.get_type::<ByteSequence>())].into_py_dict(py);
|
||||||
|
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s = ByteSequence([1, 2, 3]); del s[0]; assert list(s) == [2, 3]"
|
||||||
|
);
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s = ByteSequence([1, 2, 3]); del s[1]; assert list(s) == [1, 3]"
|
||||||
|
);
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s = ByteSequence([1, 2, 3]); del s[-1]; assert list(s) == [1, 2]"
|
||||||
|
);
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s = ByteSequence([1, 2, 3]); del s[-2]; assert list(s) == [1, 3]"
|
||||||
|
);
|
||||||
|
py_expect_exception!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s = ByteSequence([1, 2, 3]); del s[-4]; print(list(s))",
|
||||||
|
PyIndexError
|
||||||
|
);
|
||||||
|
py_expect_exception!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s = ByteSequence([1, 2, 3]); del s[4]",
|
||||||
|
PyIndexError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_contains() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = seq_dict(py);
|
||||||
|
|
||||||
|
py_assert!(py, *d, "1 in s");
|
||||||
|
py_assert!(py, *d, "2 in s");
|
||||||
|
py_assert!(py, *d, "3 in s");
|
||||||
|
py_assert!(py, *d, "4 not in s");
|
||||||
|
py_assert!(py, *d, "'hello' not in s");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_concat() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = seq_dict(py);
|
||||||
|
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s1 = ByteSequence([1, 2]); s2 = ByteSequence([3, 4]); assert list(s1 + s2) == [1, 2, 3, 4]"
|
||||||
|
);
|
||||||
|
py_expect_exception!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s1 = ByteSequence([1, 2]); s2 = 'hello'; s1 + s2",
|
||||||
|
PyTypeError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inplace_concat() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = seq_dict(py);
|
||||||
|
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s += ByteSequence([4, 5]); assert list(s) == [1, 2, 3, 4, 5]"
|
||||||
|
);
|
||||||
|
py_expect_exception!(py, *d, "s += 'hello'", PyTypeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_repeat() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = seq_dict(py);
|
||||||
|
|
||||||
|
py_run!(py, *d, "s2 = s * 2; assert list(s2) == [1, 2, 3, 1, 2, 3]");
|
||||||
|
py_expect_exception!(py, *d, "s2 = s * -1", PyValueError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inplace_repeat() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let d = [("ByteSequence", py.get_type::<ByteSequence>())].into_py_dict(py);
|
||||||
|
|
||||||
|
py_run!(
|
||||||
|
py,
|
||||||
|
*d,
|
||||||
|
"s = ByteSequence([1, 2]); s *= 3; assert list(s) == [1, 2, 1, 2, 1, 2]"
|
||||||
|
);
|
||||||
|
py_expect_exception!(py, *d, "s = ByteSequence([1, 2]); s *= -1", PyValueError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that #[pyo3(get, set)] works correctly for Vec<PyObject>
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct GenericList {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
items: Vec<PyObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generic_list_get() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let list: PyObject = GenericList {
|
||||||
|
items: [1, 2, 3].iter().map(|i| i.to_object(py)).collect(),
|
||||||
|
}
|
||||||
|
.into_py(py);
|
||||||
|
|
||||||
|
py_assert!(py, list, "list.items == [1, 2, 3]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generic_list_set() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let list = PyCell::new(py, GenericList { items: vec![] }).unwrap();
|
||||||
|
|
||||||
|
py_run!(py, list, "list.items = [1, 2, 3]");
|
||||||
|
assert_eq!(
|
||||||
|
list.borrow().items,
|
||||||
|
vec![1.to_object(py), 2.to_object(py), 3.to_object(py)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct OptionList {
|
||||||
|
#[pyo3(get, set)]
|
||||||
|
items: Vec<Option<i64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyproto]
|
||||||
|
impl PySequenceProtocol for OptionList {
|
||||||
|
fn __getitem__(&self, idx: isize) -> PyResult<Option<i64>> {
|
||||||
|
match self.items.get(idx as usize) {
|
||||||
|
Some(x) => Ok(*x),
|
||||||
|
None => Err(PyIndexError::new_err("Index out of bounds")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_option_list_get() {
|
||||||
|
// Regression test for #798
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let list = PyCell::new(
|
||||||
|
py,
|
||||||
|
OptionList {
|
||||||
|
items: vec![Some(1), None],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
py_assert!(py, list, "list[0] == 1");
|
||||||
|
py_assert!(py, list, "list[1] == None");
|
||||||
|
py_expect_exception!(py, list, "list[2]", PyIndexError);
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
|
||||||
|
|
|
|
||||||
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
|
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
|
||||||
note: required by a bound in `ThreadCheckerInherited`
|
note: required by a bound in `ThreadCheckerInherited`
|
||||||
--> src/class/impl_.rs
|
--> src/impl_/pyclass.rs
|
||||||
|
|
|
|
||||||
| pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
|
| pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
|
||||||
| ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited`
|
| ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited`
|
||||||
|
|
|
@ -6,9 +6,9 @@ error[E0277]: `*mut pyo3::Python<'static>` cannot be shared between threads safe
|
||||||
|
|
|
|
||||||
= help: within `pyo3::Python<'_>`, the trait `Sync` is not implemented for `*mut pyo3::Python<'static>`
|
= help: within `pyo3::Python<'_>`, the trait `Sync` is not implemented for `*mut pyo3::Python<'static>`
|
||||||
= note: required because it appears within the type `PhantomData<*mut pyo3::Python<'static>>`
|
= note: required because it appears within the type `PhantomData<*mut pyo3::Python<'static>>`
|
||||||
= note: required because it appears within the type `pyo3::impl_::not_send::NotSend`
|
= note: required because it appears within the type `impl_::not_send::NotSend`
|
||||||
= note: required because it appears within the type `(&GILGuard, pyo3::impl_::not_send::NotSend)`
|
= note: required because it appears within the type `(&GILGuard, impl_::not_send::NotSend)`
|
||||||
= note: required because it appears within the type `PhantomData<(&GILGuard, pyo3::impl_::not_send::NotSend)>`
|
= note: required because it appears within the type `PhantomData<(&GILGuard, impl_::not_send::NotSend)>`
|
||||||
= note: required because it appears within the type `pyo3::Python<'_>`
|
= note: required because it appears within the type `pyo3::Python<'_>`
|
||||||
= note: required because of the requirements on the impl of `Send` for `&pyo3::Python<'_>`
|
= note: required because of the requirements on the impl of `Send` for `&pyo3::Python<'_>`
|
||||||
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send.rs:4:22: 4:38]`
|
= note: required because it appears within the type `[closure@$DIR/tests/ui/not_send.rs:4:22: 4:38]`
|
||||||
|
|
|
@ -11,7 +11,7 @@ note: required because it appears within the type `NotThreadSafe`
|
||||||
5 | struct NotThreadSafe {
|
5 | struct NotThreadSafe {
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
note: required by a bound in `ThreadCheckerStub`
|
note: required by a bound in `ThreadCheckerStub`
|
||||||
--> src/class/impl_.rs
|
--> src/impl_/pyclass.rs
|
||||||
|
|
|
|
||||||
| pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
|
| pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
|
||||||
| ^^^^ required by this bound in `ThreadCheckerStub`
|
| ^^^^ required by this bound in `ThreadCheckerStub`
|
||||||
|
|
Loading…
Reference in New Issue