pyproto: split into new feature

This commit is contained in:
David Hewitt 2022-01-11 07:50:02 +00:00
parent 6db7dd76d3
commit 558549e1c2
45 changed files with 2084 additions and 1342 deletions

View File

@ -52,7 +52,7 @@ serde_json = "1.0.61"
pyo3-build-config = { path = "pyo3-build-config", version = "0.15.1", features = ["resolve-config"] }
[features]
default = ["macros"]
default = ["macros", "pyproto"]
# Enables macros: #[pyclass], #[pymodule], #[pyfunction] etc.
macros = ["pyo3-macros", "indoc", "unindent"]
@ -60,6 +60,9 @@ macros = ["pyo3-macros", "indoc", "unindent"]
# Enables multiple #[pymethods] per #[pyclass]
multiple-pymethods = ["inventory", "pyo3-macros/multiple-pymethods"]
# Enables deprecated #[pyproto] macro
pyproto = ["pyo3-macros/pyproto"]
# Use this feature when building an extension module.
# It tells the linker to keep the python symbols unresolved,
# so that the module can also be used with statically linked python interpreters.
@ -83,7 +86,7 @@ nightly = []
# Activates all additional features
# 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]]
name = "bench_call"

View File

@ -3,9 +3,6 @@
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
list_all_additive_features:
@echo $(ALL_ADDITIVE_FEATURES)
test: lint test_py
cargo test
cargo test --features="abi3"

View File

@ -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 IS_GC: bool = false;
const IS_BASETYPE: bool = false;
const IS_SUBCLASS: bool = false;
type Layout = PyCell<MyClass>;
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])) {
use pyo3::class::impl_::*;
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<MyClass>::new();
visitor(collector.py_methods());
visitor(collector.py_class_descriptors());
@ -870,23 +870,23 @@ impl pyo3::class::impl_::PyClassImpl for MyClass {
visitor(collector.number_protocol_methods());
}
fn get_new() -> Option<pyo3::ffi::newfunc> {
use pyo3::class::impl_::*;
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
use pyo3::class::impl_::*;
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.alloc_impl()
}
fn get_free() -> Option<pyo3::ffi::freefunc> {
use pyo3::class::impl_::*;
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.free_impl()
}
fn for_each_proto_slot(visitor: &mut dyn FnMut(&[pyo3::ffi::PyType_Slot])) {
// Implementation which uses dtolnay specialization to load all slots.
use pyo3::class::impl_::*;
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
visitor(collector.object_protocol_slots());
visitor(collector.number_protocol_slots());

View File

@ -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.
### `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`
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:

View File

@ -22,3 +22,6 @@ pyo3-build-config = { path = "../pyo3-build-config", version = "0.15.1", feature
version = "1"
default-features = false
features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"]
[features]
pyproto = []

View File

@ -9,6 +9,7 @@
mod utils;
mod attributes;
#[cfg(feature = "pyproto")]
mod defs;
mod deprecations;
mod frompyobject;
@ -16,11 +17,13 @@ mod konst;
mod method;
mod module;
mod params;
#[cfg(feature = "pyproto")]
mod proto_method;
mod pyclass;
mod pyfunction;
mod pyimpl;
mod pymethod;
#[cfg(feature = "pyproto")]
mod pyproto;
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 pyfunction::{build_py_function, PyFunctionOptions};
pub use pyimpl::{build_py_methods, PyClassMethodsType};
#[cfg(feature = "pyproto")]
pub use pyproto::build_py_proto;
pub use utils::get_doc;
pub use wrap::{wrap_pyfunction_impl, wrap_pymodule_impl, WrapPyFunctionArgs};

View File

@ -566,23 +566,23 @@ impl<'a> FnSpec<'a> {
let doc = &self.doc;
match self.convention {
CallingConvention::Noargs => quote! {
_pyo3::class::methods::PyMethodDef::noargs(
_pyo3::impl_::pymethods::PyMethodDef::noargs(
#python_name,
_pyo3::class::methods::PyCFunction(#wrapper),
_pyo3::impl_::pymethods::PyCFunction(#wrapper),
#doc,
)
},
CallingConvention::Fastcall => quote! {
_pyo3::class::methods::PyMethodDef::fastcall_cfunction_with_keywords(
_pyo3::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords(
#python_name,
_pyo3::class::methods::PyCFunctionFastWithKeywords(#wrapper),
_pyo3::impl_::pymethods::PyCFunctionFastWithKeywords(#wrapper),
#doc,
)
},
CallingConvention::Varargs => quote! {
_pyo3::class::methods::PyMethodDef::cfunction_with_keywords(
_pyo3::impl_::pymethods::PyMethodDef::cfunction_with_keywords(
#python_name,
_pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
_pyo3::impl_::pymethods::PyCFunctionWithKeywords(#wrapper),
#doc,
)
},

View File

@ -511,11 +511,11 @@ fn unit_variants_as_descriptors<'a>(
.map(|var| gen_py_const(&cls_type, &variant_to_attribute(var)));
quote! {
impl _pyo3::class::impl_::PyClassDescriptors<#cls>
for _pyo3::class::impl_::PyClassImplCollector<#cls>
impl _pyo3::impl_::pyclass::PyClassDescriptors<#cls>
for _pyo3::impl_::pyclass::PyClassImplCollector<#cls>
{
fn py_class_descriptors(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#py_methods),*];
fn py_class_descriptors(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] = &[#(#py_methods),*];
METHODS
}
}
@ -574,11 +574,11 @@ fn impl_descriptors(
.collect::<syn::Result<_>>()?;
Ok(quote! {
impl _pyo3::class::impl_::PyClassDescriptors<#cls>
for _pyo3::class::impl_::PyClassImplCollector<#cls>
impl _pyo3::impl_::pyclass::PyClassDescriptors<#cls>
for _pyo3::impl_::pyclass::PyClassImplCollector<#cls>
{
fn py_class_descriptors(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#py_methods),*];
fn py_class_descriptors(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] = &[#(#py_methods),*];
METHODS
}
}
@ -676,7 +676,7 @@ impl<'a> PyClassImplsBuilder<'a> {
};
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 {
quote! { _pyo3::PyAny }
};
@ -749,13 +749,13 @@ impl<'a> PyClassImplsBuilder<'a> {
};
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 {
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 {
quote! { _pyo3::class::impl_::ThreadCheckerStub<#cls> }
quote! { _pyo3::impl_::pyclass::ThreadCheckerStub<#cls> }
};
let (for_each_py_method, methods_protos, inventory, inventory_class) = match self
@ -775,13 +775,13 @@ impl<'a> PyClassImplsBuilder<'a> {
);
(
quote! {
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::class::impl_::PyClassImpl>::Inventory>() {
visitor(_pyo3::class::impl_::PyClassInventory::methods(inventory));
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::impl_::pyclass::PyClassImpl>::Inventory>() {
visitor(_pyo3::impl_::pyclass::PyClassInventory::methods(inventory));
}
},
quote! {
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::class::impl_::PyClassImpl>::Inventory>() {
visitor(_pyo3::class::impl_::PyClassInventory::slots(inventory));
for inventory in _pyo3::inventory::iter::<<Self as _pyo3::impl_::pyclass::PyClassImpl>::Inventory>() {
visitor(_pyo3::impl_::pyclass::PyClassInventory::slots(inventory));
}
},
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! {
impl _pyo3::class::impl_::PyClassImpl for #cls {
impl _pyo3::impl_::pyclass::PyClassImpl for #cls {
const DOC: &'static str = #doc;
const IS_GC: bool = #is_gc;
const IS_BASETYPE: bool = #is_basetype;
@ -802,48 +831,36 @@ impl<'a> PyClassImplsBuilder<'a> {
#inventory
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();
#for_each_py_method;
visitor(collector.py_class_descriptors());
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());
#pyproto_method_visitors
}
fn get_new() -> ::std::option::Option<_pyo3::ffi::newfunc> {
use _pyo3::class::impl_::*;
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_alloc() -> ::std::option::Option<_pyo3::ffi::allocfunc> {
use _pyo3::class::impl_::*;
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.alloc_impl()
}
fn get_free() -> ::std::option::Option<_pyo3::ffi::freefunc> {
use _pyo3::class::impl_::*;
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.free_impl()
}
fn for_each_proto_slot(visitor: &mut dyn ::std::ops::FnMut(&[_pyo3::ffi::PyType_Slot])) {
// Implementation which uses dtolnay specialization to load all slots.
use _pyo3::class::impl_::*;
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
// This depends on Python implementation detail;
// an old slot entry will be overriden by newer ones.
visitor(collector.py_class_default_slots());
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());
#pyproto_proto_visitors
#methods_protos
}
@ -861,7 +878,7 @@ impl<'a> PyClassImplsBuilder<'a> {
self.attr.freelist.as_ref().map_or(quote!{}, |freelist| {
quote! {
impl _pyo3::class::impl_::PyClassWithFreeList for #cls {
impl _pyo3::impl_::pyclass::PyClassWithFreeList for #cls {
#[inline]
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 _;
@ -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]
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]
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] {
self.methods
}

View File

@ -190,7 +190,7 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
_pyo3::class::PyMethodDefType::ClassAttribute({
_pyo3::class::PyClassAttributeDef::new(
#python_name,
_pyo3::class::methods::PyClassAttributeFactory({
_pyo3::impl_::pymethods::PyClassAttributeFactory({
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
#deprecations
_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 {
#(#method_defs)*
}
impl ::pyo3::class::impl_::PyClassDefaultSlots<#cls>
for ::pyo3::class::impl_::PyClassImplCollector<#cls> {
impl ::pyo3::impl_::pyclass::PyClassDefaultSlots<#cls>
for ::pyo3::impl_::pyclass::PyClassImplCollector<#cls> {
fn py_class_default_slots(self) -> &'static [::pyo3::ffi::PyType_Slot] {
&[#(#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 {
quote! {
impl _pyo3::class::impl_::PyMethods<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::impl_::pyclass::PyMethods<#ty>
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
{
fn py_methods(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] = &[#(#methods),*];
fn py_methods(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] = &[#(#methods),*];
METHODS
}
}
@ -266,7 +266,7 @@ fn add_shared_proto_slots(
let first_implemented = implemented_proto_fragments.remove($first);
let second_implemented = implemented_proto_fragments.remove($second);
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 {
quote! {
impl _pyo3::class::impl_::PyMethodsProtocolSlots<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::impl_::pyclass::PyMethodsProtocolSlots<#ty>
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
{
fn methods_protocol_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
&[#(#proto_impls),*]
@ -317,7 +317,7 @@ fn submit_methods_inventory(
) -> TokenStream {
quote! {
_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),*])
}
}

View File

@ -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 = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
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> {
::std::option::Option::Some({
#wrapper
@ -229,7 +229,7 @@ fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
_pyo3::class::PyMethodDefType::ClassAttribute({
_pyo3::class::PyClassAttributeDef::new(
#python_name,
_pyo3::class::methods::PyClassAttributeFactory({
_pyo3::impl_::pymethods::PyClassAttributeFactory({
fn __wrap(py: _pyo3::Python<'_>) -> _pyo3::PyObject {
#deprecations
_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
_pyo3::class::PySetterDef::new(
#python_name,
_pyo3::class::methods::PySetter({
_pyo3::impl_::pymethods::PySetter({
unsafe extern "C" fn __wrap(
_slf: *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
_pyo3::class::PyGetterDef::new(
#python_name,
_pyo3::class::methods::PyGetter({
_pyo3::impl_::pymethods::PyGetter({
unsafe extern "C" fn __wrap(
_slf: *mut _pyo3::ffi::PyObject,
_: *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 ret_ty = ret_ty.ffi_type();
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]
unsafe fn #method(

View File

@ -109,11 +109,11 @@ fn impl_normal_methods(
let methods_trait = proto.methods_trait();
let methods_trait_methods = proto.methods_trait_methods();
quote! {
impl _pyo3::class::impl_::#methods_trait<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::impl_::pyclass::#methods_trait<#ty>
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
{
fn #methods_trait_methods(self) -> &'static [_pyo3::class::methods::PyMethodDefType] {
static METHODS: &[_pyo3::class::methods::PyMethodDefType] =
fn #methods_trait_methods(self) -> &'static [_pyo3::impl_::pymethods::PyMethodDefType] {
static METHODS: &[_pyo3::impl_::pymethods::PyMethodDefType] =
&[#(#py_methods),*];
METHODS
}
@ -153,8 +153,8 @@ fn impl_proto_methods(
}
quote! {
impl _pyo3::class::impl_::#slots_trait<#ty>
for _pyo3::class::impl_::PyClassImplCollector<#ty>
impl _pyo3::impl_::pyclass::#slots_trait<#ty>
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
{
fn #slots_trait_slots(self) -> &'static [_pyo3::ffi::PyType_Slot] {
&[#(#tokens),*]

View File

@ -16,6 +16,8 @@ proc-macro = true
[features]
multiple-pymethods = []
pyproto = ["pyo3-macros-backend/pyproto"]
[dependencies]
proc-macro2 = { version = "1", default-features = false }
quote = "1"

View File

@ -9,9 +9,8 @@ use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use pyo3_macros_backend::{
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,
wrap_pymodule_impl, PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions,
WrapPyFunctionArgs,
get_doc, process_functions_in_module, pymodule_impl, wrap_pyfunction_impl, wrap_pymodule_impl,
PyClassArgs, PyClassMethodsType, PyFunctionOptions, PyModuleOptions, WrapPyFunctionArgs,
};
use quote::quote;
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
/// [5]: ../class/iter/trait.PyIterProtocol.html
#[proc_macro_attribute]
#[cfg(feature = "pyproto")]
pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream {
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!(
#ast

View File

@ -11,10 +11,7 @@ impl Subclassable {
fn new() -> Self {
Subclassable {}
}
}
#[pyproto]
impl pyo3::PyObjectProtocol for Subclassable {
fn __str__(&self) -> PyResult<&'static str> {
Ok("Subclassable")
}

View File

@ -12,37 +12,6 @@ use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput};
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyObject};
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
#[allow(unused_variables)]
pub trait PyObjectProtocol<'p>: PyClass {
@ -202,3 +171,5 @@ py_func_set_del!(
__delattr__
);
py_unary_func!(bool, PyObjectBoolProtocol, T::__bool__, c_int);
pub use crate::pyclass::CompareOp;

View File

@ -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
}

View File

@ -4,8 +4,7 @@
use crate::callback::IntoPyCallbackOutput;
use crate::derive_utils::TryFromPyCell;
use crate::err::PyResult;
use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python};
use crate::{PyClass, PyObject};
/// Python Iterator Interface.
///
@ -73,49 +72,4 @@ pub trait PyIterNextProtocol<'p>: PyIterProtocol<'p> {
py_unarys_func!(iter, PyIterIterProtocol, Self::__iter__);
py_unarys_func!(iternext, PyIterNextProtocol, Self::__next__);
/// 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())),
}
}
}
pub use crate::pyclass::{IterNextOutput, PyIterNextOutput};

View File

@ -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.")
}

View File

@ -10,12 +10,10 @@ pub mod basic;
pub mod buffer;
pub mod descr;
pub mod gc;
#[doc(hidden)]
pub mod impl_;
pub mod iter;
pub mod mapping;
#[doc(hidden)]
pub mod methods;
pub use crate::impl_::pymethods as methods;
pub mod number;
pub mod pyasync;
pub mod sequence;

View File

@ -9,8 +9,7 @@
use crate::callback::IntoPyCallbackOutput;
use crate::derive_utils::TryFromPyCell;
use crate::err::PyResult;
use crate::{ffi, IntoPy, IntoPyPointer, PyClass, PyObject, Python};
use crate::{PyClass, PyObject};
/// 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!(anext, PyAsyncAnextProtocol, Self::__anext__);
/// 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())),
}
}
}
pub use crate::pyclass::{IterANextOutput, PyIterANextOutput};

View File

@ -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.
#[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
}

View File

@ -1,5 +1,5 @@
use crate::{
class::methods::PyMethodDef, derive_utils::PyFunctionArguments, types::PyCFunction, PyResult,
derive_utils::PyFunctionArguments, impl_::pymethods::PyMethodDef, types::PyCFunction, PyResult,
};
pub trait PyFunctionDef {

View File

@ -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.
#[cfg(Py_3_8)]
@ -31,3 +35,216 @@ impl IPowModulo {
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.")
}

View File

@ -300,6 +300,30 @@ pub use crate::python::{Python, PythonVersionInfo};
pub use crate::type_object::PyTypeInfo;
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")]
#[doc(hidden)]
pub use {
@ -317,6 +341,7 @@ mod internal_tricks;
pub mod buffer;
#[doc(hidden)]
pub mod callback;
#[cfg(feature = "pyproto")]
pub mod class;
pub mod conversion;
mod conversions;
@ -350,12 +375,16 @@ pub use crate::conversions::*;
)]
#[cfg(feature = "macros")]
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")]
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")]

View File

@ -22,6 +22,7 @@ pub use crate::python::Python;
pub use crate::types::{PyAny, PyModule};
#[cfg(feature = "macros")]
pub use pyo3_macros::{
pyclass, pyfunction, pymethods, pymodule, pyproto, wrap_pyfunction, FromPyObject,
};
pub use pyo3_macros::{pyclass, pyfunction, pymethods, pymodule, wrap_pyfunction, FromPyObject};
#[cfg(all(feature = "macros", feature = "pyproto"))]
pub use pyo3_macros::pyproto;

View File

@ -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"
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_init::PyClassInitializer;
use crate::type_object::{PyLayout, PySizedLayout};
use crate::types::PyAny;
use crate::{class::impl_::PyClassBaseType, class::impl_::PyClassThreadChecker};
use crate::{
conversion::{AsPyPointer, FromPyPointer, ToPyObject},
ffi::PyBaseObject_Type,

View File

@ -1,12 +1,10 @@
//! `PyClass` and related traits.
use crate::{
class::impl_::{
assign_sequence_item_from_mapping, fallback_new, get_sequence_item_from_mapping,
tp_dealloc, PyClassImpl,
},
callback::IntoPyCallbackOutput,
ffi,
impl_::pyclass::{PyClassDict, PyClassWeakRef},
PyCell, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
impl_::pyclass::{assign_sequence_item_from_mapping, get_sequence_item_from_mapping, fallback_new, tp_dealloc, PyClassDict, PyClassImpl, PyClassWeakRef},
IntoPy, IntoPyPointer, PyCell, PyErr, PyMethodDefType, PyNativeType, PyObject, PyResult,
PyTypeInfo, Python,
};
use std::{
convert::TryInto,
@ -435,3 +433,130 @@ const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
doc: 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())),
}
}
}

View File

@ -1,7 +1,6 @@
//! Contains initialization utilities for `#[pyclass]`.
use crate::class::impl_::PyClassThreadChecker;
use crate::impl_::pyclass::{PyClassDict, PyClassWeakRef};
use crate::{callback::IntoPyCallbackOutput, class::impl_::PyClassBaseType};
use crate::callback::IntoPyCallbackOutput;
use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python};
use crate::{
ffi::PyTypeObject,

View File

@ -2,8 +2,9 @@ use crate::derive_utils::PyFunctionArguments;
use crate::exceptions::PyValueError;
use crate::prelude::*;
use crate::{
class::methods::{self, PyMethodDef},
ffi, types, AsPyPointer,
ffi,
impl_::pymethods::{self, PyMethodDef},
types, AsPyPointer,
};
use std::os::raw::c_void;
@ -64,7 +65,11 @@ impl PyCFunction {
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> {
Self::internal_new(
PyMethodDef::cfunction_with_keywords(name, methods::PyCFunctionWithKeywords(fun), doc),
PyMethodDef::cfunction_with_keywords(
name,
pymethods::PyCFunctionWithKeywords(fun),
doc,
),
py_or_module,
)
}
@ -77,7 +82,7 @@ impl PyCFunction {
py_or_module: PyFunctionArguments<'a>,
) -> PyResult<&'a Self> {
Self::internal_new(
PyMethodDef::noargs(name, methods::PyCFunction(fun), doc),
PyMethodDef::noargs(name, pymethods::PyCFunction(fun), doc),
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",
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())

View File

@ -196,10 +196,10 @@ macro_rules! pyobject_native_type_sized {
($name:ty, $layout:path $(;$generics:ident)*) => {
unsafe impl $crate::type_object::PyLayout<$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 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>;
}
}

View File

@ -1,5 +1,6 @@
#![allow(deprecated)] // for deprecated protocol methods
#![cfg(feature = "macros")]
#![cfg(feature = "pyproto")]
use pyo3::class::basic::CompareOp;
use pyo3::class::*;

View File

@ -1,10 +1,7 @@
#![cfg(feature = "macros")]
#![cfg(not(Py_LIMITED_API))]
use pyo3::{
buffer::PyBuffer, class::PyBufferProtocol, exceptions::PyBufferError, ffi, prelude::*,
AsPyPointer,
};
use pyo3::{buffer::PyBuffer, exceptions::PyBufferError, ffi, prelude::*, AsPyPointer};
use std::{
ffi::CStr,
os::raw::{c_int, c_void},
@ -28,9 +25,13 @@ struct TestBufferErrors {
error: Option<TestGetBufferError>,
}
#[pyproto]
impl PyBufferProtocol for TestBufferErrors {
fn bf_getbuffer(slf: PyRefMut<Self>, view: *mut ffi::Py_buffer, flags: c_int) -> PyResult<()> {
#[pymethods]
impl TestBufferErrors {
unsafe fn __getbuffer__(
slf: PyRefMut<Self>,
view: *mut ffi::Py_buffer,
flags: c_int,
) -> PyResult<()> {
if 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"));
}
unsafe {
(*view).obj = ffi::_Py_NewRef(slf.as_ptr());
}
let bytes = &slf.buf;
unsafe {
(*view).buf = bytes.as_ptr() as *mut c_void;
(*view).len = bytes.len() as isize;
(*view).readonly = 1;
@ -80,12 +78,9 @@ impl PyBufferProtocol for TestBufferErrors {
IncorrectAlignment => (*view).buf = (*view).buf.add(1),
}
}
}
Ok(())
}
fn bf_releasebuffer(_slf: PyRefMut<Self>, _view: *mut ffi::Py_buffer) {}
}
#[test]

View File

@ -1,4 +1,5 @@
#![cfg(feature = "macros")]
#![cfg(feature = "pyproto")]
#![cfg(not(Py_LIMITED_API))]
use pyo3::buffer::PyBuffer;

View File

@ -3,7 +3,6 @@
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyString, PyTuple};
use pyo3::PyMappingProtocol;
#[macro_use]
mod common;
@ -39,8 +38,8 @@ pub struct PyA {
foo: Option<String>,
}
#[pyproto]
impl PyMappingProtocol for PyA {
#[pymethods]
impl PyA {
fn __getitem__(&self, key: String) -> pyo3::PyResult<String> {
if key == "t" {
Ok("bar".into())

View File

@ -1,4 +1,5 @@
#![cfg(feature = "macros")]
#![cfg(feature = "pyproto")] // FIXME: #[pymethods] to support gc protocol
use pyo3::class::PyGCProtocol;
use pyo3::class::PyTraverseError;

282
tests/test_gc_pyproto.rs Normal file
View File

@ -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);
}
}

View File

@ -7,7 +7,6 @@ use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::IntoPyDict;
use pyo3::types::PyList;
use pyo3::PyMappingProtocol;
mod common;
@ -33,10 +32,7 @@ impl Mapping {
})
}
}
}
#[pyproto]
impl PyMappingProtocol for Mapping {
fn __len__(&self) -> usize {
self.index.len()
}

View File

@ -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);
}

View File

@ -1,4 +1,5 @@
#![cfg(feature = "macros")]
#![cfg(feature = "pyproto")]
use pyo3::class::{
PyAsyncProtocol, PyDescrProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol,

View File

@ -3,7 +3,7 @@
//! Test slf: PyRef/PyMutRef<Self>(especially, slf.into::<Py>) works
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyString};
use pyo3::{PyCell, PyIterProtocol};
use pyo3::PyCell;
use std::collections::HashMap;
mod common;
@ -54,9 +54,9 @@ struct Iter {
idx: usize,
}
#[pyproto]
impl PyIterProtocol for Iter {
fn __iter__(slf: PyRef<Self>) -> PyRef<'p, Self> {
#[pymethods]
impl Iter {
fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
slf
}

View File

@ -1,4 +1,5 @@
#![cfg(feature = "macros")]
#![cfg(feature = "pyproto")] // FIXME: change this to use #[pymethods] once supports sequence protocol
use pyo3::class::PySequenceProtocol;
use pyo3::exceptions::{PyIndexError, PyValueError};

View File

@ -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);
}

View File

@ -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 by a bound in `ThreadCheckerInherited`
--> src/class/impl_.rs
--> src/impl_/pyclass.rs
|
| pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
| ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited`

View File

@ -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>`
= 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 `(&GILGuard, pyo3::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 `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, impl_::not_send::NotSend)>`
= 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 it appears within the type `[closure@$DIR/tests/ui/not_send.rs:4:22: 4:38]`

View File

@ -11,7 +11,7 @@ note: required because it appears within the type `NotThreadSafe`
5 | struct NotThreadSafe {
| ^^^^^^^^^^^^^
note: required by a bound in `ThreadCheckerStub`
--> src/class/impl_.rs
--> src/impl_/pyclass.rs
|
| pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
| ^^^^ required by this bound in `ThreadCheckerStub`