Skip to content

Commit 69dba08

Browse files
committed
Use PyMethodsImpl instead of *ProtocolImpl::methods
1 parent da22eec commit 69dba08

15 files changed

+118
-632
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2424
- The `GILGuard` returned from `Python::acquire_gil` will now only assume responsiblity for freeing owned references on drop if no other `GILPool` or `GILGuard` exists. This ensures that multiple calls to the safe api `Python::acquire_gil` cannot lead to dangling references. [#893](https://github.com/PyO3/pyo3/pull/893)
2525
- The trait `ObjectProtocol` has been removed, and all the methods from the trait have been moved to `PyAny`. [#911](https://github.com/PyO3/pyo3/pull/911)
2626
- The exception to this is `ObjectProtocol::None`, which has simply been removed. Use `Python::None` instead.
27+
- No `#![feature(specialization)]` in user code. [#917](https://github.com/PyO3/pyo3/pull/917)
2728

2829
### Removed
2930
- `PyMethodsProtocol` is now renamed to `PyMethodsImpl` and hidden. [#889](https://github.com/PyO3/pyo3/pull/889)
3031
- `num-traits` is no longer a dependency. [#895](https://github.com/PyO3/pyo3/pull/895)
3132
- `ObjectProtocol`. [#911](https://github.com/PyO3/pyo3/pull/911)
33+
- All `*ProtocolImpl` traits. [#917](https://github.com/PyO3/pyo3/pull/917)
3234

3335
### Fixed
3436
- `__radd__` and other `__r*__` methods now correctly work with operators. [#839](https://github.com/PyO3/pyo3/pull/839)

guide/src/class.md

+72-71
Original file line numberDiff line numberDiff line change
@@ -17,76 +17,7 @@ struct MyClass {
1717
The above example generates implementations for [`PyTypeInfo`], [`PyTypeObject`],
1818
and [`PyClass`] for `MyClass`.
1919

20-
Specifically, the following implementation is generated:
21-
22-
```rust
23-
use pyo3::prelude::*;
24-
25-
/// Class for demonstration
26-
struct MyClass {
27-
num: i32,
28-
debug: bool,
29-
}
30-
31-
impl pyo3::pyclass::PyClassAlloc for MyClass {}
32-
33-
unsafe impl pyo3::PyTypeInfo for MyClass {
34-
type Type = MyClass;
35-
type BaseType = PyAny;
36-
type BaseLayout = pyo3::pycell::PyCellBase<PyAny>;
37-
type Layout = PyCell<Self>;
38-
type Initializer = PyClassInitializer<Self>;
39-
type AsRefTarget = PyCell<Self>;
40-
41-
const NAME: &'static str = "MyClass";
42-
const MODULE: Option<&'static str> = None;
43-
const DESCRIPTION: &'static str = "Class for demonstration";
44-
const FLAGS: usize = 0;
45-
46-
#[inline]
47-
fn type_object() -> &'static pyo3::ffi::PyTypeObject {
48-
use pyo3::type_object::LazyStaticType;
49-
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
50-
TYPE_OBJECT.get_or_init::<Self>()
51-
}
52-
}
53-
54-
impl pyo3::pyclass::PyClass for MyClass {
55-
type Dict = pyo3::pyclass_slots::PyClassDummySlot;
56-
type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
57-
type BaseNativeType = PyAny;
58-
}
59-
60-
impl pyo3::IntoPy<PyObject> for MyClass {
61-
fn into_py(self, py: pyo3::Python) -> pyo3::PyObject {
62-
pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py)
63-
}
64-
}
65-
66-
pub struct Pyo3MethodsInventoryForMyClass {
67-
methods: &'static [pyo3::class::PyMethodDefType],
68-
}
69-
70-
impl pyo3::class::methods::PyMethodsInventory for Pyo3MethodsInventoryForMyClass {
71-
fn new(methods: &'static [pyo3::class::PyMethodDefType]) -> Self {
72-
Self { methods }
73-
}
74-
75-
fn get_methods(&self) -> &'static [pyo3::class::PyMethodDefType] {
76-
self.methods
77-
}
78-
}
79-
80-
impl pyo3::class::methods::PyMethodsImpl for MyClass {
81-
type Methods = Pyo3MethodsInventoryForMyClass;
82-
}
83-
84-
pyo3::inventory::collect!(Pyo3MethodsInventoryForMyClass);
85-
# let gil = Python::acquire_gil();
86-
# let py = gil.python();
87-
# let cls = py.get_type::<MyClass>();
88-
# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'")
89-
```
20+
If you curious what `#[pyclass]` generates, see [How methods are implemented](#how-methods-are-implemented) section.
9021

9122
## Adding the class to a module
9223

@@ -944,7 +875,77 @@ pyclass dependent on whether there is an impl block, we'd need to implement the
944875
`#[pyclass]` and override the implementation in `#[pymethods]`, which is to the best of my knowledge
945876
only possible with the specialization feature, which can't be used on stable.
946877

947-
To escape this we use [inventory](https://github.com/dtolnay/inventory), which allows us to collect `impl`s from arbitrary source code by exploiting some binary trick. See [inventory: how it works](https://github.com/dtolnay/inventory#how-it-works) and `pyo3_derive_backend::py_class::impl_inventory` for more details.
878+
To escape this we use [inventory](https://github.com/dtolnay/inventory),
879+
which allows us to collect `impl`s from arbitrary source code by exploiting some binary trick.
880+
See [inventory: how it works](https://github.com/dtolnay/inventory#how-it-works) and `pyo3_derive_backend::py_class` for more details.
881+
882+
Specifically, the following implementation is generated:
883+
884+
```rust
885+
use pyo3::prelude::*;
886+
887+
/// Class for demonstration
888+
struct MyClass {
889+
num: i32,
890+
debug: bool,
891+
}
892+
893+
impl pyo3::pyclass::PyClassAlloc for MyClass {}
894+
895+
unsafe impl pyo3::PyTypeInfo for MyClass {
896+
type Type = MyClass;
897+
type BaseType = PyAny;
898+
type BaseLayout = pyo3::pycell::PyCellBase<PyAny>;
899+
type Layout = PyCell<Self>;
900+
type Initializer = PyClassInitializer<Self>;
901+
type AsRefTarget = PyCell<Self>;
902+
903+
const NAME: &'static str = "MyClass";
904+
const MODULE: Option<&'static str> = None;
905+
const DESCRIPTION: &'static str = "Class for demonstration";
906+
const FLAGS: usize = 0;
907+
908+
#[inline]
909+
fn type_object() -> &'static pyo3::ffi::PyTypeObject {
910+
use pyo3::type_object::LazyStaticType;
911+
static TYPE_OBJECT: LazyStaticType = LazyStaticType::new();
912+
TYPE_OBJECT.get_or_init::<Self>()
913+
}
914+
}
915+
916+
impl pyo3::pyclass::PyClass for MyClass {
917+
type Dict = pyo3::pyclass_slots::PyClassDummySlot;
918+
type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
919+
type BaseNativeType = PyAny;
920+
}
921+
922+
impl pyo3::IntoPy<PyObject> for MyClass {
923+
fn into_py(self, py: pyo3::Python) -> pyo3::PyObject {
924+
pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py)
925+
}
926+
}
927+
928+
pub struct Pyo3MethodsInventoryForMyClass {
929+
methods: &'static [pyo3::class::PyMethodDefType],
930+
}
931+
impl pyo3::class::methods::PyMethodsInventory for Pyo3MethodsInventoryForMyClass {
932+
fn new(methods: &'static [pyo3::class::PyMethodDefType]) -> Self {
933+
Self { methods }
934+
}
935+
fn get(&self) -> &'static [pyo3::class::PyMethodDefType] {
936+
self.methods
937+
}
938+
}
939+
impl pyo3::class::methods::PyMethodsImpl for MyClass {
940+
type Methods = Pyo3MethodsInventoryForMyClass;
941+
}
942+
pyo3::inventory::collect!(Pyo3MethodsInventoryForMyClass);
943+
# let gil = Python::acquire_gil();
944+
# let py = gil.python();
945+
# let cls = py.get_type::<MyClass>();
946+
# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'")
947+
```
948+
948949

949950
[`GILGuard`]: https://docs.rs/pyo3/latest/pyo3/struct.GILGuard.html
950951
[`PyGCProtocol`]: https://docs.rs/pyo3/latest/pyo3/class/gc/trait.PyGCProtocol.html

pyo3-derive-backend/src/pyclass.rs

+7-13
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,9 @@ fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
208208
Ok(descs)
209209
}
210210

211-
/// The orphan rule disallows using a generic inventory struct, so we create the whole boilerplate
212-
/// once per class
213-
fn impl_inventory(cls: &syn::Ident) -> TokenStream {
214-
// Try to build a unique type that gives a hint about it's function when
215-
// it comes up in error messages
211+
/// To allow multiple #[pymethods]/#[pyproto] block, we define inventory types.
212+
fn impl_methods_inventory(cls: &syn::Ident) -> TokenStream {
213+
// Try to build a unique type for better error messages
216214
let name = format!("Pyo3MethodsInventoryFor{}", cls);
217215
let inventory_cls = syn::Ident::new(&name, Span::call_site());
218216

@@ -221,15 +219,11 @@ fn impl_inventory(cls: &syn::Ident) -> TokenStream {
221219
pub struct #inventory_cls {
222220
methods: &'static [pyo3::class::PyMethodDefType],
223221
}
224-
225222
impl pyo3::class::methods::PyMethodsInventory for #inventory_cls {
226223
fn new(methods: &'static [pyo3::class::PyMethodDefType]) -> Self {
227-
Self {
228-
methods
229-
}
224+
Self { methods }
230225
}
231-
232-
fn get_methods(&self) -> &'static [pyo3::class::PyMethodDefType] {
226+
fn get(&self) -> &'static [pyo3::class::PyMethodDefType] {
233227
self.methods
234228
}
235229
}
@@ -345,7 +339,7 @@ fn impl_class(
345339
quote! {}
346340
};
347341

348-
let inventory_impl = impl_inventory(&cls);
342+
let impl_inventory = impl_methods_inventory(&cls);
349343

350344
let base = &attr.base;
351345
let flags = &attr.flags;
@@ -418,7 +412,7 @@ fn impl_class(
418412

419413
#into_pyobject
420414

421-
#inventory_impl
415+
#impl_inventory
422416

423417
#extra
424418

pyo3-derive-backend/src/pyproto.rs

+30-29
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result<TokenStream> {
3636
));
3737
};
3838

39-
let tokens = impl_proto_impl(&ast.self_ty, &mut ast.items, proto);
39+
let tokens = impl_proto_impl(&ast.self_ty, &mut ast.items, proto)?;
4040

4141
// attach lifetime
4242
let mut seg = path.segments.pop().unwrap().into_value();
@@ -57,53 +57,54 @@ fn impl_proto_impl(
5757
ty: &syn::Type,
5858
impls: &mut Vec<syn::ImplItem>,
5959
proto: &defs::Proto,
60-
) -> TokenStream {
61-
let mut tokens = TokenStream::new();
60+
) -> syn::Result<TokenStream> {
61+
let mut trait_impls = TokenStream::new();
6262
let mut py_methods = Vec::new();
6363

6464
for iimpl in impls.iter_mut() {
6565
if let syn::ImplItem::Method(ref mut met) = iimpl {
6666
if let Some(m) = proto.get_proto(&met.sig.ident) {
67-
impl_method_proto(ty, &mut met.sig, m).to_tokens(&mut tokens);
67+
impl_method_proto(ty, &mut met.sig, m).to_tokens(&mut trait_impls);
6868
}
6969
if let Some(m) = proto.get_method(&met.sig.ident) {
7070
let name = &met.sig.ident;
71-
let proto: syn::Path = syn::parse_str(m.proto).unwrap();
72-
73-
let fn_spec = match FnSpec::parse(&met.sig, &mut met.attrs, false) {
74-
Ok(fn_spec) => fn_spec,
75-
Err(err) => return err.to_compile_error(),
76-
};
77-
let meth = pymethod::impl_proto_wrap(ty, &fn_spec);
71+
let fn_spec = FnSpec::parse(&met.sig, &mut met.attrs, false)?;
72+
let method = pymethod::impl_proto_wrap(ty, &fn_spec);
7873
let coexist = if m.can_coexist {
74+
// We need METH_COEXIST here to prevent __add__ from overriding __radd__
7975
quote!(pyo3::ffi::METH_COEXIST)
8076
} else {
8177
quote!(0)
8278
};
79+
// TODO(kngwyu): doc
8380
py_methods.push(quote! {
84-
impl #proto for #ty
85-
{
86-
#[inline]
87-
fn #name() -> Option<pyo3::class::methods::PyMethodDef> {
88-
#meth
89-
90-
Some(pyo3::class::PyMethodDef {
91-
ml_name: stringify!(#name),
92-
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
93-
// We need METH_COEXIST here to prevent __add__ from overriding __radd__
94-
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS | #coexist,
95-
ml_doc: ""
96-
})
81+
pyo3::class::PyMethodDefType::Method({
82+
#method
83+
pyo3::class::PyMethodDef {
84+
ml_name: stringify!(#name),
85+
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
86+
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS | #coexist,
87+
ml_doc: ""
9788
}
98-
}
89+
})
9990
});
10091
}
10192
}
10293
}
10394

104-
quote! {
105-
#tokens
106-
107-
#(#py_methods)*
95+
if py_methods.is_empty() {
96+
return Ok(quote! { #trait_impls });
10897
}
98+
let inventory_submission = quote! {
99+
pyo3::inventory::submit! {
100+
#![crate = pyo3] {
101+
type ProtoInventory = <#ty as pyo3::class::methods::PyMethodsImpl>::Methods;
102+
<ProtoInventory as pyo3::class::methods::PyMethodsInventory>::new(&[#(#py_methods),*])
103+
}
104+
}
105+
};
106+
Ok(quote! {
107+
#trait_impls
108+
#inventory_submission
109+
})
109110
}

0 commit comments

Comments
 (0)