From f334bac071bd4834dd46173c5db337cf9fade432 Mon Sep 17 00:00:00 2001 From: Ming-Yang Lu Date: Thu, 9 Feb 2023 23:27:38 +0800 Subject: [PATCH] Embed field info into pylassimpl --- pyo3-macros-backend/src/inspect.rs | 54 +++++++++++++++++++++++ pyo3-macros-backend/src/pyclass.rs | 15 +++++-- pyo3-macros-backend/src/pyimpl.rs | 12 +++-- src/impl_/pyclass.rs | 4 ++ src/inspect/classes.rs | 71 +++++++++++++++++++----------- src/inspect/interface.rs | 12 ++--- 6 files changed, 129 insertions(+), 39 deletions(-) diff --git a/pyo3-macros-backend/src/inspect.rs b/pyo3-macros-backend/src/inspect.rs index e8070a537d2..095c111dd1e 100644 --- a/pyo3-macros-backend/src/inspect.rs +++ b/pyo3-macros-backend/src/inspect.rs @@ -12,6 +12,60 @@ use crate::pyclass::{FieldPyO3Options, get_class_python_name}; use crate::PyClassArgs; use crate::pymethod::PyMethod; +pub(crate) fn generate_class_fields( + cls: &Ident, + args: &PyClassArgs, + field_options: &Vec<(&syn::Field, FieldPyO3Options)>, +) -> Vec { + let ident_prefix = format_ident!("_path_{}", cls); + let class_field_info = format_ident!("{}_struct_field_info", ident_prefix); + let class_info = format_ident!("{}_struct_info", ident_prefix); + + let name = Literal::string(&*get_class_python_name(cls, args).to_string()); + + let mut fields: Vec = vec![]; + for (field, options) in field_options { + let typ = generate_type(cls.to_string().as_str(), &field.ty) + .map(|it| Box::new(it) as Box) + .unwrap_or_else(|| Box::new(cls)); + let name = options.name.as_ref() + .map(|it| Literal::string(&*it.value.0.to_string())) + .or_else(|| field.ident.as_ref().map(|it| Literal::string(&*it.to_string()))); + + if let Some(name) = name { + if options.get.is_some() { + fields.push(quote! { + &_pyo3::inspect::fields::FieldInfo { + name: #name, + kind: _pyo3::inspect::fields::FieldKind::Getter, + py_type: ::std::option::Option::Some(|| <#typ as _pyo3::inspect::types::WithTypeInfo>::type_output()), + arguments: &[], + } + }); + } + + if options.set.is_some() { + fields.push(quote! { + &_pyo3::inspect::fields::FieldInfo { + name: #name, + kind: _pyo3::inspect::fields::FieldKind::Setter, + py_type: ::std::option::Option::Some(|| _pyo3::inspect::types::TypeInfo::None), + arguments: &[ + _pyo3::inspect::fields::ArgumentInfo { + name: #name, + kind: _pyo3::inspect::fields::ArgumentKind::Position, + py_type: ::std::option::Option::Some(|| <#typ as _pyo3::inspect::types::WithTypeInfo>::type_output()), + default_value: false, + is_modified: false, + } + ], + } + }); + } + } + } + fields +} /// Extracts inspection information from the `#[pyclass]` macro. /// /// Extracted information: diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 5ad5d6e315b..2cf1e8039cd 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -23,7 +23,7 @@ use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{parse_quote, spanned::Spanned, Result, Token}; -use crate::inspect::generate_class_inspection; +use crate::inspect::{generate_class_inspection, generate_class_fields}; /// If the class is derived from a Rust `struct` or `enum`. #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -354,7 +354,8 @@ fn impl_class( ) -> syn::Result { let pytypeinfo_impl = impl_pytypeinfo(cls, args, Some(&args.options.deprecations)); - let class_info = generate_class_inspection(cls, args, &field_options); + // let class_info = generate_class_inspection(cls, args, &field_options); + let field_infos = generate_class_fields(cls, args, &field_options); let py_class_impl = PyClassImplsBuilder::new( cls, @@ -362,6 +363,7 @@ fn impl_class( methods_type, descriptors_to_items(cls, field_options)?, vec![], + field_infos, ) .doc(doc) .impl_all()?; @@ -374,7 +376,7 @@ fn impl_class( #py_class_impl - #class_info + // #class_info }; }) } @@ -620,6 +622,7 @@ fn impl_enum_class( methods_type, enum_default_methods(cls, variants.iter().map(|v| (v.ident, v.python_name()))), default_slots, + vec![], ) .doc(doc) .impl_all()?; @@ -802,6 +805,7 @@ struct PyClassImplsBuilder<'a> { methods_type: PyClassMethodsType, default_methods: Vec, default_slots: Vec, + field_infos: Vec, doc: Option, } @@ -812,6 +816,7 @@ impl<'a> PyClassImplsBuilder<'a> { methods_type: PyClassMethodsType, default_methods: Vec, default_slots: Vec, + field_infos: Vec, ) -> Self { Self { cls, @@ -819,6 +824,7 @@ impl<'a> PyClassImplsBuilder<'a> { methods_type, default_methods, default_slots, + field_infos, doc: None, } } @@ -1001,6 +1007,8 @@ impl<'a> PyClassImplsBuilder<'a> { let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def); let freelist_slots = self.freelist_slots(); + let field_infos = self.field_infos.iter().clone(); + let deprecations = &self.attr.deprecations; let class_mutability = if self.attr.options.frozen.is_some() { @@ -1058,6 +1066,7 @@ impl<'a> PyClassImplsBuilder<'a> { static INTRINSIC_ITEMS: PyClassItems = PyClassItems { methods: &[#(#default_method_defs),*], slots: &[#(#default_slot_defs),* #(#freelist_slots),*], + field_infos: &[#(#field_infos),*], }; PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items) } diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index 715c1487937..b1cb5d4c047 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -158,13 +158,13 @@ pub fn impl_methods( add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments); - let impl_info = generate_impl_inspection(ty, field_infos); - trait_impls.push(impl_info); + // let impl_info = generate_impl_inspection(ty, field_infos); + // trait_impls.push(impl_info); let krate = get_pyo3_crate(&options.krate); let items = match methods_type { - PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls), + PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls, field_infos), PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls), }; @@ -217,7 +217,10 @@ fn impl_py_methods( ty: &syn::Type, methods: Vec, proto_impls: Vec, + field_infos: Vec, ) -> TokenStream { + let field_infos = field_infos.iter() + .map(|field| quote!(&#field)); quote! { impl _pyo3::impl_::pyclass::PyMethods<#ty> for _pyo3::impl_::pyclass::PyClassImplCollector<#ty> @@ -225,7 +228,8 @@ fn impl_py_methods( fn py_methods(self) -> &'static _pyo3::impl_::pyclass::PyClassItems { static ITEMS: _pyo3::impl_::pyclass::PyClassItems = _pyo3::impl_::pyclass::PyClassItems { methods: &[#(#methods),*], - slots: &[#(#proto_impls),*] + slots: &[#(#proto_impls),*], + field_infos: &[#(#field_infos),*], }; &ITEMS } diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index a2671748d74..df9f38a1c6d 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -125,6 +125,8 @@ impl Copy for PyClassImplCollector {} pub struct PyClassItems { pub methods: &'static [PyMethodDefType], pub slots: &'static [ffi::PyType_Slot], + #[cfg(feature="experimental-inspect")] + pub field_infos: &'static [&'static crate::inspect::fields::FieldInfo<'static>], } // Allow PyClassItems in statics @@ -831,6 +833,8 @@ impl PyMethods for &'_ PyClassImplCollector { &PyClassItems { methods: &[], slots: &[], + #[cfg(feature="experimental-inspect")] + field_infos: &[], } } } diff --git a/src/inspect/classes.rs b/src/inspect/classes.rs index 25e285eb43d..41797280e92 100644 --- a/src/inspect/classes.rs +++ b/src/inspect/classes.rs @@ -1,32 +1,32 @@ -use crate::inspect::fields::FieldInfo; +use crate::{impl_::pyclass::PyClassImpl, inspect::fields::FieldInfo, PyTypeInfo}; /// Information about a Python class. #[derive(Debug)] -pub struct ClassInfo<'a> { +pub struct ClassInfo { /// Base information about the class. - pub class: &'a ClassStructInfo<'a>, + pub class: ClassStructInfo, /// Information found in `#[pymethods]`. - pub fields: &'a [&'a FieldInfo<'a>], + pub fields: Vec<&'static FieldInfo<'static>>, } /// Subset of available information about a Python class, including only what is available by parsing the `#[pyclass]` /// block (methods are missing). #[derive(Debug)] -pub struct ClassStructInfo<'a> { - pub name: &'a str, - pub base: Option<&'a str>, - pub fields: &'a [&'a FieldInfo<'a>], +pub struct ClassStructInfo { + pub name: &'static str, + pub base: Option<&'static str>, + pub fields: Vec<&'static FieldInfo<'static>>, } -impl<'a> ClassInfo<'a> { +impl ClassInfo { /// The Python name of this class. - pub fn name(&'a self) -> &'a str { + pub fn name(&self) -> &str { self.class.name } /// The Python's base class. - pub fn base(&'a self) -> Option<&'a str> { + pub fn base(&self) -> Option<&'static str> { self.class.base } @@ -35,32 +35,51 @@ impl<'a> ClassInfo<'a> { /// This includes: /// - struct attributes annotated with `#[getter]` or `#[setter]` /// - methods that appear in a `#[pymethods]` block - pub fn fields(&'a self) -> impl Iterator> + 'a { - self.class.fields - .iter() - .chain(self.fields) + pub fn fields(&self) -> impl Iterator> { + self.class.fields.iter().cloned().chain(self.fields.iter().cloned()) } } -pub trait InspectClass<'a> { - fn inspect() -> ClassInfo<'a>; +pub trait InspectClass { + fn inspect() -> ClassInfo; } -pub trait InspectStruct<'a> { - fn inspect_struct() -> &'a ClassStructInfo<'a>; +pub trait InspectStruct { + fn inspect_struct() -> ClassStructInfo; } -pub trait InspectImpl<'a> { - fn inspect_impl() -> &'a [&'a FieldInfo<'a>]; +pub trait InspectImpl { + fn inspect_impl() -> Vec<&'static FieldInfo<'static>>; } -impl<'a, T> InspectClass<'a> for T - where T: InspectStruct<'a>, T: InspectImpl<'a> +impl InspectClass for T +where + T: crate::PyClass, { - fn inspect() -> ClassInfo<'a> { + fn inspect() -> ClassInfo { + let name = ::NAME; + let fields: Vec<&FieldInfo<'static>> = ::items_iter() + .flat_map(|item| item.field_infos.iter().cloned()) + .collect(); + ClassInfo { - class: Self::inspect_struct(), - fields: Self::inspect_impl(), + class: ClassStructInfo { + name, + base: None, + fields: vec![], + }, + fields, } } } + +// impl<'a, T> InspectClass<'a> for T +// where T: InspectStruct<'a>, T: InspectImpl<'a> +// { +// fn inspect() -> ClassInfo<'a> { +// ClassInfo { +// class: Self::inspect_struct(), +// fields: Self::inspect_impl(), +// } +// } +// } diff --git a/src/inspect/interface.rs b/src/inspect/interface.rs index 77e709d32c5..944e29c86e9 100644 --- a/src/inspect/interface.rs +++ b/src/inspect/interface.rs @@ -8,12 +8,12 @@ use crate::inspect::fields::{ArgumentInfo, ArgumentKind, FieldInfo, FieldKind}; /// /// Instances are created with [`InterfaceGenerator::new`]. /// The documentation is generated via the [`Display`] implementation. -pub struct InterfaceGenerator<'a> { - info: ClassInfo<'a> +pub struct InterfaceGenerator { + info: ClassInfo, } -impl<'a> InterfaceGenerator<'a> { - pub fn new(info: ClassInfo<'a>) -> Self { +impl InterfaceGenerator { + pub fn new(info: ClassInfo) -> Self { Self { info } @@ -130,7 +130,7 @@ impl<'a> InterfaceGenerator<'a> { } } -impl<'a> Display for InterfaceGenerator<'a> { +impl Display for InterfaceGenerator { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.class_header(f)?; @@ -141,7 +141,7 @@ impl<'a> Display for InterfaceGenerator<'a> { } for field in self.info.fields() { - Self::field(*field, f)?; + Self::field(field, f)?; } Ok(())