Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: simpler pyclass internals #2157

Merged
merged 4 commits into from Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -50,7 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reduce generated LLVM code size (to improve compile times) for:
- internal `handle_panic` helper [#2074](https://github.com/PyO3/pyo3/pull/2074)
- `#[pyfunction]` and `#[pymethods]` argument extraction [#2075](https://github.com/PyO3/pyo3/pull/2075) [#2085](https://github.com/PyO3/pyo3/pull/2085)
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081)
- `#[pyclass]` type object creation [#2076](https://github.com/PyO3/pyo3/pull/2076) [#2081](https://github.com/PyO3/pyo3/pull/2081) [#2157](https://github.com/PyO3/pyo3/pull/2157)
- `__ipow__` now supports modulo argument on Python 3.8+. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- `pyo3-macros-backend` is now compiled with PyO3 cfgs to enable different magic method definitions based on version. [#2083](https://github.com/PyO3/pyo3/pull/2083)
- `_PyCFunctionFast` now correctly reflects the signature defined in the [Python docs](https://docs.python.org/3/c-api/structures.html#c._PyCFunctionFast). [#2126](https://github.com/PyO3/pyo3/pull/2126)
Expand Down
15 changes: 0 additions & 15 deletions guide/src/class.md
Expand Up @@ -981,21 +981,6 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
visitor(&INTRINSIC_ITEMS);
visitor(collector.py_methods());
}
fn get_new() -> Option<pyo3::ffi::newfunc> {
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_alloc() -> Option<pyo3::ffi::allocfunc> {
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.alloc_impl()
}
fn get_free() -> Option<pyo3::ffi::freefunc> {
use pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.free_impl()
}
}
# Python::with_gil(|py| {
# let cls = py.get_type::<MyClass>();
Expand Down
142 changes: 70 additions & 72 deletions pyo3-macros-backend/src/pyclass.rs
Expand Up @@ -387,6 +387,7 @@ fn impl_class(
attr,
methods_type,
descriptors_to_items(cls, field_options)?,
vec![],
)
.doc(doc)
.impl_all();
Expand Down Expand Up @@ -497,23 +498,15 @@ fn impl_enum_class(
let cls = enum_.ident;
let variants = enum_.variants;
let pytypeinfo = impl_pytypeinfo(cls, args, None);
let pyclass_impls = PyClassImplsBuilder::new(
cls,
args,
methods_type,
unit_variants_as_items(cls, variants.iter().map(|v| v.ident)),
)
.doc(doc)
.impl_all();

let default_repr_impl = {
let default_repr_impl: syn::ImplItemMethod = {
let variants_repr = variants.iter().map(|variant| {
let variant_name = variant.ident;
// Assuming all variants are unit variants because they are the only type we support.
let repr = format!("{}.{}", cls, variant_name);
quote! { #cls::#variant_name => #repr, }
});
quote! {
syn::parse_quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
#[pyo3(name = "__repr__")]
Expand All @@ -533,7 +526,7 @@ fn impl_enum_class(
let variant_name = variant.ident;
quote! { #cls::#variant_name => #cls::#variant_name as #repr_type, }
});
quote! {
syn::parse_quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
#[pyo3(name = "__int__")]
Expand All @@ -553,7 +546,7 @@ fn impl_enum_class(
Ok(true.to_object(py)),
}
});
quote! {
syn::parse_quote! {
#[doc(hidden)]
#[allow(non_snake_case)]
#[pyo3(name = "__richcmp__")]
Expand Down Expand Up @@ -584,8 +577,17 @@ fn impl_enum_class(
}
};

let default_items =
gen_default_items(cls, vec![default_repr_impl, default_richcmp, default_int]);
let mut default_methods = vec![default_repr_impl, default_richcmp, default_int];

let pyclass_impls = PyClassImplsBuilder::new(
cls,
args,
methods_type,
enum_default_methods(cls, variants.iter().map(|v| v.ident)),
enum_default_slots(cls, &mut default_methods),
)
.doc(doc)
.impl_all();

Ok(quote! {
const _: () = {
Expand All @@ -595,15 +597,17 @@ fn impl_enum_class(

#pyclass_impls

#default_items
impl #cls {
#(#default_methods)*
}
};
})
}

fn unit_variants_as_items<'a>(
fn enum_default_methods<'a>(
cls: &'a syn::Ident,
variant_names: impl IntoIterator<Item = &'a syn::Ident>,
) -> TokenStream {
unit_variant_names: impl IntoIterator<Item = &'a syn::Ident>,
) -> Vec<TokenStream> {
let cls_type = syn::parse_quote!(#cls);
let variant_to_attribute = |ident: &syn::Ident| ConstSpec {
rust_ident: ident.clone(),
Expand All @@ -613,16 +617,17 @@ fn unit_variants_as_items<'a>(
deprecations: Default::default(),
},
};
let py_methods = variant_names
unit_variant_names
.into_iter()
.map(|var| gen_py_const(&cls_type, &variant_to_attribute(var)));
.map(|var| gen_py_const(&cls_type, &variant_to_attribute(var)))
.collect()
}

quote! {
_pyo3::impl_::pyclass::PyClassItems {
methods: &[#(#py_methods),*],
slots: &[]
}
}
fn enum_default_slots(
cls: &syn::Ident,
default_items: &mut [syn::ImplItemMethod],
) -> Vec<TokenStream> {
gen_default_items(cls, default_items).collect()
}

fn extract_variant_data(variant: &syn::Variant) -> syn::Result<PyClassEnumVariant> {
Expand All @@ -637,9 +642,9 @@ fn extract_variant_data(variant: &syn::Variant) -> syn::Result<PyClassEnumVarian
fn descriptors_to_items(
cls: &syn::Ident,
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
) -> syn::Result<TokenStream> {
) -> syn::Result<Vec<TokenStream>> {
let ty = syn::parse_quote!(#cls);
let py_methods: Vec<TokenStream> = field_options
field_options
.into_iter()
.enumerate()
.flat_map(|(field_index, (field, options))| {
Expand Down Expand Up @@ -671,14 +676,7 @@ fn descriptors_to_items(

name_err.into_iter().chain(getter).chain(setter)
})
.collect::<syn::Result<_>>()?;

Ok(quote! {
_pyo3::impl_::pyclass::PyClassItems {
methods: &[#(#py_methods),*],
slots: &[]
}
})
.collect::<syn::Result<_>>()
}

fn impl_pytypeinfo(
Expand Down Expand Up @@ -722,7 +720,8 @@ struct PyClassImplsBuilder<'a> {
cls: &'a syn::Ident,
attr: &'a PyClassArgs,
methods_type: PyClassMethodsType,
default_items: TokenStream,
default_methods: Vec<TokenStream>,
default_slots: Vec<TokenStream>,
doc: Option<PythonDoc>,
}

Expand All @@ -731,13 +730,15 @@ impl<'a> PyClassImplsBuilder<'a> {
cls: &'a syn::Ident,
attr: &'a PyClassArgs,
methods_type: PyClassMethodsType,
default_items: TokenStream,
default_methods: Vec<TokenStream>,
default_slots: Vec<TokenStream>,
) -> Self {
Self {
cls,
attr,
methods_type,
default_items,
default_methods,
default_slots,
doc: None,
}
}
Expand Down Expand Up @@ -899,7 +900,9 @@ impl<'a> PyClassImplsBuilder<'a> {
None
};

let default_items = &self.default_items;
let default_methods = &self.default_methods;
let default_slots = &self.default_slots;
let freelist_slots = self.freelist_slots();

quote! {
impl _pyo3::impl_::pyclass::PyClassImpl for #cls {
Expand All @@ -916,29 +919,14 @@ impl<'a> PyClassImplsBuilder<'a> {
fn for_all_items(visitor: &mut dyn ::std::ops::FnMut(& _pyo3::impl_::pyclass::PyClassItems)) {
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
static INTRINSIC_ITEMS: PyClassItems = #default_items;
static INTRINSIC_ITEMS: PyClassItems = PyClassItems {
methods: &[#(#default_methods),*],
slots: &[#(#default_slots),* #(#freelist_slots),*],
};
visitor(&INTRINSIC_ITEMS);
// This depends on Python implementation detail;
// an old slot entry will be overriden by newer ones.
visitor(collector.pyclass_default_items());
#pymethods_items
#pyproto_items
}
fn get_new() -> ::std::option::Option<_pyo3::ffi::newfunc> {
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.new_impl()
}
fn get_alloc() -> ::std::option::Option<_pyo3::ffi::allocfunc> {
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.alloc_impl()
}
fn get_free() -> ::std::option::Option<_pyo3::ffi::freefunc> {
use _pyo3::impl_::pyclass::*;
let collector = PyClassImplCollector::<Self>::new();
collector.free_impl()
}

#dict_offset

Expand Down Expand Up @@ -967,23 +955,33 @@ impl<'a> PyClassImplsBuilder<'a> {
}
}
}
}
})
}

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::impl_::pyclass::alloc_with_freelist::<#cls>)
}
}
fn freelist_slots(&self) -> Vec<TokenStream> {
let cls = self.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::impl_::pyclass::free_with_freelist::<#cls>)
if self.attr.freelist.is_some() {
vec![
quote! {
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::Py_tp_alloc,
pfunc: _pyo3::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _,
}
}
}
})
},
quote! {
_pyo3::ffi::PyType_Slot {
slot: _pyo3::ffi::Py_tp_free,
pfunc: _pyo3::impl_::pyclass::free_with_freelist::<#cls> as *mut _,
}
},
]
} else {
Vec::new()
}
}

/// Enforce at compile time that PyGCProtocol is implemented
fn impl_gc(&self) -> TokenStream {
let cls = self.cls;
Expand Down
40 changes: 8 additions & 32 deletions pyo3-macros-backend/src/pyimpl.rs
Expand Up @@ -108,10 +108,6 @@ pub fn impl_methods(
let attrs = get_cfg_attributes(&meth.attrs);
methods.push(quote!(#(#attrs)* #token_stream));
}
GeneratedPyMethod::TraitImpl(token_stream) => {
let attrs = get_cfg_attributes(&meth.attrs);
trait_impls.push(quote!(#(#attrs)* #token_stream));
}
GeneratedPyMethod::SlotTraitImpl(method_name, token_stream) => {
implemented_proto_fragments.insert(method_name);
let attrs = get_cfg_attributes(&meth.attrs);
Expand Down Expand Up @@ -186,49 +182,29 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
}
}

pub fn gen_default_items(cls: &syn::Ident, method_defs: Vec<TokenStream>) -> TokenStream {
pub fn gen_default_items<'a>(
cls: &syn::Ident,
method_defs: &'a mut [syn::ImplItemMethod],
) -> impl Iterator<Item = TokenStream> + 'a {
// This function uses a lot of `unwrap()`; since method_defs are provided by us, they should
// all succeed.
let ty: syn::Type = syn::parse_quote!(#cls);

let mut method_defs: Vec<_> = method_defs
.into_iter()
.map(|token| syn::parse2::<syn::ImplItemMethod>(token).unwrap())
.collect();

let mut proto_impls = Vec::new();

for meth in &mut method_defs {
method_defs.iter_mut().map(move |meth| {
let options = PyFunctionOptions::from_attrs(&mut meth.attrs).unwrap();
match pymethod::gen_py_method(&ty, &mut meth.sig, &mut meth.attrs, options).unwrap() {
GeneratedPyMethod::Proto(token_stream) => {
let attrs = get_cfg_attributes(&meth.attrs);
proto_impls.push(quote!(#(#attrs)* #token_stream))
quote!(#(#attrs)* #token_stream)
}
GeneratedPyMethod::SlotTraitImpl(..) => {
panic!("SlotFragment methods cannot have default implementation!")
}
GeneratedPyMethod::Method(_) | GeneratedPyMethod::TraitImpl(_) => {
GeneratedPyMethod::Method(_) => {
panic!("Only protocol methods can have default implementation!")
}
}
}

quote! {
impl #cls {
#(#method_defs)*
}
impl _pyo3::impl_::pyclass::PyClassDefaultItems<#cls>
for _pyo3::impl_::pyclass::PyClassImplCollector<#cls> {
fn pyclass_default_items(self) -> &'static _pyo3::impl_::pyclass::PyClassItems {
static ITEMS: _pyo3::impl_::pyclass::PyClassItems = _pyo3::impl_::pyclass::PyClassItems {
methods: &[],
slots: &[#(#proto_impls),*]
};
&ITEMS
}
}
}
})
}

fn impl_py_methods(
Expand Down