From 53f2e941030aa3c8bab22758570a0f4465eddfe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 15 Jun 2022 12:52:35 +0200 Subject: [PATCH] Inspect attributes marked with `#[pyo3(get, set)]` --- pyo3-macros-backend/src/inspect.rs | 66 ++++++++++++++++++++++++++---- pyo3-macros-backend/src/pyclass.rs | 6 +-- tests/test_interface.rs | 45 +++++++++++++++++++- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/pyo3-macros-backend/src/inspect.rs b/pyo3-macros-backend/src/inspect.rs index 67a87bd5013..3046aa0e91b 100644 --- a/pyo3-macros-backend/src/inspect.rs +++ b/pyo3-macros-backend/src/inspect.rs @@ -27,9 +27,53 @@ pub(crate) fn generate_class_inspection( 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(&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 { + fields.push(quote! { + &_pyo3::inspect::fields::FieldInfo { + name: #name, + kind: _pyo3::inspect::fields::FieldKind::Getter, + py_type: ::std::option::Option::Some(|| <#typ as _pyo3::conversion::IntoPy<_>>::type_output()), + arguments: &[], + } + }); + } + + if options.set { + 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::conversion::IntoPy<_>>::type_output()), + default_value: false, + is_modified: false, + } + ], + } + }); + } + } + } + + let field_size = Literal::usize_suffixed(fields.len()); + quote! { - const #class_field_info: [&'static _pyo3::inspect::fields::FieldInfo<'static>; 0] = [ - //TODO + const #class_field_info: [&'static _pyo3::inspect::fields::FieldInfo<'static>; #field_size] = [ + #(#fields),* ]; const #class_info: _pyo3::inspect::classes::ClassStructInfo<'static> = _pyo3::inspect::classes::ClassStructInfo { @@ -102,12 +146,9 @@ pub(crate) fn generate_fields_inspection( FnType::FnModule => todo!("FnModule is not currently supported"), FnType::ClassAttribute => quote!(_pyo3::inspect::fields::FieldKind::ClassAttribute), }; - let field_type = match &field.spec.output { - Type::Path(path) if path.path.get_ident().filter(|i| i.to_string() == "Self").is_some() => { - cls.to_token_stream() - } - other => other.to_token_stream(), - }; + let field_type = generate_type(&field.spec.output) + .map(|it| it.to_token_stream()) + .unwrap_or_else(|| cls.to_token_stream()); let output = quote! { fn #field_type_fn_name() -> _pyo3::inspect::types::TypeInfo { @@ -127,6 +168,15 @@ pub(crate) fn generate_fields_inspection( (output, field_info_name) } +fn generate_type(target: &Type) -> Option { + match target { + Type::Path(path) if path.path.get_ident().filter(|i| i.to_string() == "Self").is_some() => { + None + } + other => Some(other) + } +} + /// Generates a unique identifier based on a type and (optionally) a field. /// /// For the same input values, the result should be the same output, and for different input values, diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 3e6ada7cecf..eb58906ea80 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -225,9 +225,9 @@ pub fn build_py_class( /// `#[pyo3()]` options for pyclass fields pub(crate) struct FieldPyO3Options { - get: bool, - set: bool, - name: Option, + pub(crate) get: bool, + pub(crate) set: bool, + pub(crate) name: Option, } enum FieldPyO3Option { diff --git a/tests/test_interface.rs b/tests/test_interface.rs index c77e0c8d06b..86694db1cf7 100644 --- a/tests/test_interface.rs +++ b/tests/test_interface.rs @@ -5,6 +5,7 @@ use pyo3::inspect::classes::{ClassInfo, ClassStructInfo, InspectClass}; use pyo3::inspect::fields::{ArgumentInfo, ArgumentKind, FieldInfo, FieldKind}; use pyo3::inspect::interface::InterfaceGenerator; use pyo3::inspect::types::TypeInfo; +use pyo3::types::PyType; mod common; @@ -122,7 +123,7 @@ const EXPECTED_COMPLICATED: &str = r#"class Complicated(Simple): def value(self, value: int) -> None: ... def __new__(cls, /, foo: str = ..., **parent: Any) -> None: ... @staticmethod - def static(input: Complicated) -> Complicated: ... + def staticmeth(input: Complicated) -> Complicated: ... @classmethod def classmeth(cls, /, input: Union[Complicated, str, int]) -> Complicated: ... counter: int = ... @@ -180,7 +181,7 @@ fn complicated_manual() { ], }, &FieldInfo { - name: "static", + name: "staticmeth", kind: FieldKind::StaticMethod, py_type: Some(|| TypeInfo::Class { module: None, name: "Complicated" }), arguments: &[ @@ -219,4 +220,44 @@ fn complicated_manual() { assert_eq!(EXPECTED_COMPLICATED, format!("{}", InterfaceGenerator::new(class))) } +#[pyclass] +struct Complicated { + #[pyo3(get, set)] + value: usize, +} + +#[allow(unused_variables)] +#[pymethods] +impl Complicated { + fn new(foo: PyObject, parent: PyObject) -> Self { + unreachable!("This is just a stub") + } + + #[pyo3(name = "staticmeth")] + #[staticmethod] + fn static_method(input: PyObject) -> Complicated { + unreachable!("This is just a stub") + } + + #[pyo3(name = "classmeth")] + #[classmethod] + fn class_method(cls: &PyType, input: ClassMethodInput) -> Complicated { + unreachable!("This is just a stub") + } +} + +#[derive(FromPyObject)] +enum ClassMethodInput { + // Complicated(PyCell), + String(String), + Int(usize), +} + +#[test] +fn complicated_derived() { + let inspect = Complicated::inspect(); + println!("Inspection results: {:#?}", inspect); + assert_eq!(EXPECTED_COMPLICATED, format!("{}", InterfaceGenerator::new(inspect))) +} + //endregion