pymethods: support protocols with `multiple-pymethods` feature

This commit is contained in:
David Hewitt 2021-10-18 08:30:59 +01:00
parent 4b2345fe80
commit 0e0e6f8bf5
9 changed files with 68 additions and 83 deletions

View File

@ -346,14 +346,21 @@ fn impl_methods_inventory(cls: &syn::Ident) -> TokenStream {
#[doc(hidden)] #[doc(hidden)]
pub struct #inventory_cls { pub struct #inventory_cls {
methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>, methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>,
slots: ::std::vec::Vec<::pyo3::ffi::PyType_Slot>,
} }
impl ::pyo3::class::impl_::PyMethodsInventory for #inventory_cls { impl ::pyo3::class::impl_::PyMethodsInventory for #inventory_cls {
fn new(methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>) -> Self { fn new(
Self { methods } methods: ::std::vec::Vec<::pyo3::class::PyMethodDefType>,
slots: ::std::vec::Vec<::pyo3::ffi::PyType_Slot>,
) -> Self {
Self { methods, slots }
} }
fn get(&'static self) -> &'static [::pyo3::class::PyMethodDefType] { fn methods(&'static self) -> &'static [::pyo3::class::PyMethodDefType] {
&self.methods &self.methods
} }
fn slots(&'static self) -> &'static [::pyo3::ffi::PyType_Slot] {
&self.slots
}
} }
impl ::pyo3::class::impl_::HasMethodsInventory for #cls { impl ::pyo3::class::impl_::HasMethodsInventory for #cls {
@ -450,15 +457,12 @@ fn impl_class(
}; };
let (impl_inventory, for_each_py_method) = match methods_type { let (impl_inventory, for_each_py_method) = match methods_type {
PyClassMethodsType::Specialization => ( PyClassMethodsType::Specialization => (None, quote! { visitor(collector.py_methods()); }),
::std::option::Option::None,
quote! { visitor(collector.py_methods()); },
),
PyClassMethodsType::Inventory => ( PyClassMethodsType::Inventory => (
Some(impl_methods_inventory(cls)), Some(impl_methods_inventory(cls)),
quote! { quote! {
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() { for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() {
visitor(::pyo3::class::impl_::PyMethodsInventory::get(inventory)); visitor(::pyo3::class::impl_::PyMethodsInventory::methods(inventory));
} }
}, },
), ),
@ -466,9 +470,15 @@ fn impl_class(
let methods_protos = match methods_type { let methods_protos = match methods_type {
PyClassMethodsType::Specialization => { PyClassMethodsType::Specialization => {
Some(quote! { visitor(collector.methods_protocol_slots()); }) quote! { visitor(collector.methods_protocol_slots()); }
}
PyClassMethodsType::Inventory => {
quote! {
for inventory in ::pyo3::inventory::iter::<<Self as ::pyo3::class::impl_::HasMethodsInventory>::Methods>() {
visitor(::pyo3::class::impl_::PyMethodsInventory::slots(inventory));
}
}
} }
PyClassMethodsType::Inventory => None,
}; };
let base = &attr.base; let base = &attr.base;

View File

@ -85,32 +85,29 @@ pub fn impl_methods(
} }
} }
let methods_registration = match methods_type { add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments);
PyClassMethodsType::Specialization => impl_py_methods(ty, methods),
PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods),
};
let protos_registration = match methods_type { Ok(match methods_type {
PyClassMethodsType::Specialization => { PyClassMethodsType::Specialization => {
Some(impl_protos(ty, proto_impls, implemented_proto_fragments)) let methods_registration = impl_py_methods(ty, methods);
} let protos_registration = impl_protos(ty, proto_impls);
PyClassMethodsType::Inventory => {
if proto_impls.is_empty() { quote! {
None #(#trait_impls)*
} else {
panic!( #protos_registration
"cannot implement protos in #[pymethods] using `multiple-pymethods` feature"
); #methods_registration
} }
} }
}; PyClassMethodsType::Inventory => {
let inventory = submit_methods_inventory(ty, methods, proto_impls);
quote! {
#(#trait_impls)*
Ok(quote! { #inventory
#(#trait_impls)* }
}
#protos_registration
#methods_registration
}) })
} }
@ -147,11 +144,11 @@ fn impl_py_methods(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream {
} }
} }
fn impl_protos( fn add_shared_proto_slots(
ty: &syn::Type, ty: &syn::Type,
mut proto_impls: Vec<TokenStream>, proto_impls: &mut Vec<TokenStream>,
mut implemented_proto_fragments: HashSet<String>, mut implemented_proto_fragments: HashSet<String>,
) -> TokenStream { ) {
macro_rules! try_add_shared_slot { macro_rules! try_add_shared_slot {
($first:literal, $second:literal, $slot:ident) => {{ ($first:literal, $second:literal, $slot:ident) => {{
let first_implemented = implemented_proto_fragments.remove($first); let first_implemented = implemented_proto_fragments.remove($first);
@ -184,6 +181,10 @@ fn impl_protos(
); );
try_add_shared_slot!("__pow__", "__rpow__", generate_pyclass_pow_slot); try_add_shared_slot!("__pow__", "__rpow__", generate_pyclass_pow_slot);
assert!(implemented_proto_fragments.is_empty());
}
fn impl_protos(ty: &syn::Type, proto_impls: Vec<TokenStream>) -> TokenStream {
quote! { quote! {
impl ::pyo3::class::impl_::PyMethodsProtocolSlots<#ty> impl ::pyo3::class::impl_::PyMethodsProtocolSlots<#ty>
for ::pyo3::class::impl_::PyClassImplCollector<#ty> for ::pyo3::class::impl_::PyClassImplCollector<#ty>
@ -195,16 +196,16 @@ fn impl_protos(
} }
} }
fn submit_methods_inventory(ty: &syn::Type, methods: Vec<TokenStream>) -> TokenStream { fn submit_methods_inventory(
if methods.is_empty() { ty: &syn::Type,
return TokenStream::default(); methods: Vec<TokenStream>,
} proto_impls: Vec<TokenStream>,
) -> TokenStream {
quote! { quote! {
::pyo3::inventory::submit! { ::pyo3::inventory::submit! {
#![crate = ::pyo3] { #![crate = ::pyo3] {
type Inventory = <#ty as ::pyo3::class::impl_::HasMethodsInventory>::Methods; type Inventory = <#ty as ::pyo3::class::impl_::HasMethodsInventory>::Methods;
<Inventory as ::pyo3::class::impl_::PyMethodsInventory>::new(::std::vec![#(#methods),*]) <Inventory as ::pyo3::class::impl_::PyMethodsInventory>::new(::std::vec![#(#methods),*], ::std::vec![#(#proto_impls),*])
} }
} }
} }

View File

@ -612,10 +612,13 @@ macro_rules! methods_trait {
#[cfg(all(feature = "macros", feature = "multiple-pymethods"))] #[cfg(all(feature = "macros", feature = "multiple-pymethods"))]
pub trait PyMethodsInventory: inventory::Collect { pub trait PyMethodsInventory: inventory::Collect {
/// Create a new instance /// Create a new instance
fn new(methods: Vec<PyMethodDefType>) -> Self; fn new(methods: Vec<PyMethodDefType>, slots: Vec<ffi::PyType_Slot>) -> Self;
/// Returns the methods for a single `#[pymethods] impl` block /// Returns the methods for a single `#[pymethods] impl` block
fn get(&'static self) -> &'static [PyMethodDefType]; fn methods(&'static self) -> &'static [PyMethodDefType];
/// Returns the slots for a single `#[pymethods] impl` block
fn slots(&'static self) -> &'static [ffi::PyType_Slot];
} }
/// Implemented for `#[pyclass]` in our proc macro code. /// Implemented for `#[pyclass]` in our proc macro code.
@ -663,6 +666,7 @@ slots_trait!(PyAsyncProtocolSlots, async_protocol_slots);
slots_trait!(PySequenceProtocolSlots, sequence_protocol_slots); slots_trait!(PySequenceProtocolSlots, sequence_protocol_slots);
slots_trait!(PyBufferProtocolSlots, buffer_protocol_slots); slots_trait!(PyBufferProtocolSlots, buffer_protocol_slots);
// Protocol slots from #[pymethods] if not using inventory.
#[cfg(not(feature = "multiple-pymethods"))] #[cfg(not(feature = "multiple-pymethods"))]
slots_trait!(PyMethodsProtocolSlots, methods_protocol_slots); slots_trait!(PyMethodsProtocolSlots, methods_protocol_slots);

View File

@ -1,5 +1,3 @@
#![cfg(not(feature = "multiple-pymethods"))]
use pyo3::class::basic::CompareOp; use pyo3::class::basic::CompareOp;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::py_run; use pyo3::py_run;

View File

@ -2,8 +2,6 @@ mod hygiene {
mod misc; mod misc;
mod pyclass; mod pyclass;
mod pyfunction; mod pyfunction;
// cannot implement protos in #[pymethods] using `multiple-pymethods` feature
#[cfg(not(feature = "multiple-pymethods"))]
mod pymethods; mod pymethods;
mod pymodule; mod pymodule;
mod pyproto; mod pyproto;

View File

@ -1,5 +1,3 @@
#![cfg(not(feature = "multiple-pymethods"))]
use pyo3::exceptions::PyValueError; use pyo3::exceptions::PyValueError;
use pyo3::types::{PySlice, PyType}; use pyo3::types::{PySlice, PyType};
use pyo3::{exceptions::PyAttributeError, prelude::*}; use pyo3::{exceptions::PyAttributeError, prelude::*};

View File

@ -210,30 +210,6 @@ fn sequence() {
py_expect_exception!(py, c, "c['abc']", PyTypeError); py_expect_exception!(py, c, "c['abc']", PyTypeError);
} }
#[pyclass]
struct Callable {}
#[pymethods]
impl Callable {
#[__call__]
fn __call__(&self, arg: i32) -> i32 {
arg * 6
}
}
#[test]
fn callable() {
let gil = Python::acquire_gil();
let py = gil.python();
let c = Py::new(py, Callable {}).unwrap();
py_assert!(py, c, "callable(c)");
py_assert!(py, c, "c(7) == 42");
let nc = Py::new(py, Comparisons { val: 0 }).unwrap();
py_assert!(py, nc, "not callable(nc)");
}
#[pyclass] #[pyclass]
#[derive(Debug)] #[derive(Debug)]
struct SetItem { struct SetItem {

View File

@ -6,15 +6,15 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
| |
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
note: required by a bound in `PyClassBaseType` note: required by a bound in `PyClassBaseType`
--> src/class/impl_.rs:762:1 --> src/class/impl_.rs:766:1
| |
762 | / pub trait PyClassBaseType: Sized { 766 | / pub trait PyClassBaseType: Sized {
763 | | type Dict; 767 | | type Dict;
764 | | type WeakRef; 768 | | type WeakRef;
765 | | type LayoutAsBase: PyCellLayout<Self>; 769 | | type LayoutAsBase: PyCellLayout<Self>;
... | ... |
768 | | type Initializer: PyObjectInit<Self>; 772 | | type Initializer: PyObjectInit<Self>;
769 | | } 773 | | }
| |_^ required by this bound in `PyClassBaseType` | |_^ required by this bound in `PyClassBaseType`
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)
@ -26,8 +26,8 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
| |
= note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict` = note: required because of the requirements on the impl of `PyClassBaseType` for `PyDict`
note: required by a bound in `ThreadCheckerInherited` note: required by a bound in `ThreadCheckerInherited`
--> src/class/impl_.rs:749:47 --> src/class/impl_.rs:753:47
| |
749 | pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker); 753 | pub struct ThreadCheckerInherited<T: Send, U: PyClassBaseType>(PhantomData<T>, U::ThreadChecker);
| ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited` | ^^^^^^^^^^^^^^^ required by this bound in `ThreadCheckerInherited`
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -11,8 +11,8 @@ note: required because it appears within the type `NotThreadSafe`
5 | struct NotThreadSafe { 5 | struct NotThreadSafe {
| ^^^^^^^^^^^^^ | ^^^^^^^^^^^^^
note: required by a bound in `ThreadCheckerStub` note: required by a bound in `ThreadCheckerStub`
--> src/class/impl_.rs:706:33 --> src/class/impl_.rs:710:33
| |
706 | pub struct ThreadCheckerStub<T: Send>(PhantomData<T>); 710 | pub struct ThreadCheckerStub<T: Send>(PhantomData<T>);
| ^^^^ required by this bound in `ThreadCheckerStub` | ^^^^ required by this bound in `ThreadCheckerStub`
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)