Skip to content

Commit

Permalink
Embed field info into pylassimpl
Browse files Browse the repository at this point in the history
  • Loading branch information
op8867555 committed Feb 9, 2023
1 parent c6210e6 commit f334bac
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 39 deletions.
54 changes: 54 additions & 0 deletions pyo3-macros-backend/src/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenStream> {
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<TokenStream> = 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<dyn ToTokens>)
.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:
Expand Down
15 changes: 12 additions & 3 deletions pyo3-macros-backend/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -354,14 +354,16 @@ fn impl_class(
) -> syn::Result<TokenStream> {
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,
args,
methods_type,
descriptors_to_items(cls, field_options)?,
vec![],
field_infos,
)
.doc(doc)
.impl_all()?;
Expand All @@ -374,7 +376,7 @@ fn impl_class(

#py_class_impl

#class_info
// #class_info
};
})
}
Expand Down Expand Up @@ -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()?;
Expand Down Expand Up @@ -802,6 +805,7 @@ struct PyClassImplsBuilder<'a> {
methods_type: PyClassMethodsType,
default_methods: Vec<MethodAndMethodDef>,
default_slots: Vec<MethodAndSlotDef>,
field_infos: Vec<TokenStream>,
doc: Option<PythonDoc>,
}

Expand All @@ -812,13 +816,15 @@ impl<'a> PyClassImplsBuilder<'a> {
methods_type: PyClassMethodsType,
default_methods: Vec<MethodAndMethodDef>,
default_slots: Vec<MethodAndSlotDef>,
field_infos: Vec<TokenStream>,
) -> Self {
Self {
cls,
attr,
methods_type,
default_methods,
default_slots,
field_infos,
doc: None,
}
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
}
Expand Down
12 changes: 8 additions & 4 deletions pyo3-macros-backend/src/pyimpl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};

Expand Down Expand Up @@ -217,15 +217,19 @@ fn impl_py_methods(
ty: &syn::Type,
methods: Vec<TokenStream>,
proto_impls: Vec<TokenStream>,
field_infos: Vec<syn::Ident>,
) -> TokenStream {
let field_infos = field_infos.iter()
.map(|field| quote!(&#field));
quote! {
impl _pyo3::impl_::pyclass::PyMethods<#ty>
for _pyo3::impl_::pyclass::PyClassImplCollector<#ty>
{
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
}
Expand Down
4 changes: 4 additions & 0 deletions src/impl_/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ impl<T> Copy for PyClassImplCollector<T> {}
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
Expand Down Expand Up @@ -831,6 +833,8 @@ impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
&PyClassItems {
methods: &[],
slots: &[],
#[cfg(feature="experimental-inspect")]
field_infos: &[],
}
}
}
Expand Down
71 changes: 45 additions & 26 deletions src/inspect/classes.rs
Original file line number Diff line number Diff line change
@@ -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
}

Expand All @@ -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<Item=&'a &'a FieldInfo<'a>> + 'a {
self.class.fields
.iter()
.chain(self.fields)
pub fn fields(&self) -> impl Iterator<Item = &FieldInfo<'static>> {
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<T> InspectClass for T
where
T: crate::PyClass,
{
fn inspect() -> ClassInfo<'a> {
fn inspect() -> ClassInfo {
let name = <T as PyTypeInfo>::NAME;
let fields: Vec<&FieldInfo<'static>> = <T as PyClassImpl>::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(),
// }
// }
// }
12 changes: 6 additions & 6 deletions src/inspect/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)?;

Expand All @@ -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(())
Expand Down

0 comments on commit f334bac

Please sign in to comment.