Allow `#[classattr]` on associated constants

This commit is contained in:
scalexm 2020-05-07 21:20:34 +02:00
parent ab374b40da
commit f6ac9a0212
6 changed files with 101 additions and 6 deletions

View File

@ -604,6 +604,20 @@ be mutated at all:
pyo3::py_run!(py, my_class, "my_class.my_attribute = 'foo'")
```
If the class attribute is defined with `const` code only, one can also annotate associated
constants:
```rust
# use pyo3::prelude::*;
# #[pyclass]
# struct MyClass {}
#[pymethods]
impl MyClass {
#[classattr]
const MY_CONST_ATTRIBUTE: &'static str = "foobar";
}
```
## Callable objects
To specify a custom `__call__` method for a custom class, the method needs to be annotated with

View File

@ -0,0 +1,34 @@
use crate::pyfunction::parse_name_attribute;
use syn::ext::IdentExt;
#[derive(Clone, PartialEq, Debug)]
pub struct ConstSpec {
pub is_class_attr: bool,
pub python_name: syn::Ident,
}
impl ConstSpec {
// For now, the only valid attribute is `#[classattr]`.
pub fn parse(name: &syn::Ident, attrs: &mut Vec<syn::Attribute>) -> syn::Result<ConstSpec> {
let mut new_attrs = Vec::new();
let mut is_class_attr = false;
for attr in attrs.iter() {
if let syn::Meta::Path(name) = attr.parse_meta()? {
if name.is_ident("classattr") {
is_class_attr = true;
continue;
}
}
new_attrs.push(attr.clone());
}
attrs.clear();
attrs.extend(new_attrs);
Ok(ConstSpec {
is_class_attr,
python_name: parse_name_attribute(attrs)?.unwrap_or_else(|| name.unraw()),
})
}
}

View File

@ -5,6 +5,7 @@
mod defs;
mod func;
mod konst;
mod method;
mod module;
mod pyclass;

View File

@ -24,9 +24,18 @@ pub fn impl_methods(ty: &syn::Type, impls: &mut Vec<syn::ImplItem>) -> syn::Resu
let mut methods = Vec::new();
let mut cfg_attributes = Vec::new();
for iimpl in impls.iter_mut() {
if let syn::ImplItem::Method(ref mut meth) = iimpl {
methods.push(pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs)?);
cfg_attributes.push(get_cfg_attributes(&meth.attrs));
match iimpl {
syn::ImplItem::Method(meth) => {
methods.push(pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs)?);
cfg_attributes.push(get_cfg_attributes(&meth.attrs));
}
syn::ImplItem::Const(konst) => {
if let Some(meth) = pymethod::gen_py_const(ty, &konst.ident, &mut konst.attrs)? {
methods.push(meth);
}
cfg_attributes.push(get_cfg_attributes(&konst.attrs));
}
_ => (),
}
}

View File

@ -1,4 +1,5 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::konst::ConstSpec;
use crate::method::{FnArg, FnSpec, FnType};
use crate::utils;
use proc_macro2::{Span, TokenStream};
@ -31,7 +32,7 @@ pub fn gen_py_method(
FnType::FnClass => impl_py_method_def_class(&spec, &impl_wrap_class(cls, &spec)),
FnType::FnStatic => impl_py_method_def_static(&spec, &impl_wrap_static(cls, &spec)),
FnType::ClassAttribute => {
impl_py_class_attribute(&spec, &impl_wrap_class_attribute(cls, &spec))
impl_py_method_class_attribute(&spec, &impl_wrap_class_attribute(cls, &spec))
}
FnType::Getter => impl_py_getter_def(
&spec.python_name,
@ -62,6 +63,23 @@ fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
Ok(())
}
pub fn gen_py_const(
cls: &syn::Type,
name: &syn::Ident,
attrs: &mut Vec<syn::Attribute>,
) -> syn::Result<Option<TokenStream>> {
let spec = ConstSpec::parse(name, attrs)?;
if spec.is_class_attr {
let wrapper = quote! {
fn __wrap(py: pyo3::Python<'_>) -> pyo3::PyObject {
pyo3::IntoPy::into_py(#cls::#name, py)
}
};
return Ok(Some(impl_py_const_class_attribute(&spec, &wrapper)));
}
Ok(None)
}
/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap(cls: &syn::Type, spec: &FnSpec<'_>, noargs: bool) -> TokenStream {
let body = impl_call(cls, &spec);
@ -249,7 +267,8 @@ pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
}
}
/// Generate a wrapper for initialization of a class attribute.
/// Generate a wrapper for initialization of a class attribute from a method
/// annotated with `#[classattr]`.
/// To be called in `pyo3::pyclass::initialize_type_object`.
pub fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let name = &spec.name;
@ -630,7 +649,21 @@ pub fn impl_py_method_def_static(spec: &FnSpec, wrapper: &TokenStream) -> TokenS
}
}
pub fn impl_py_class_attribute(spec: &FnSpec<'_>, wrapper: &TokenStream) -> TokenStream {
pub fn impl_py_method_class_attribute(spec: &FnSpec<'_>, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
quote! {
pyo3::class::PyMethodDefType::ClassAttribute({
#wrapper
pyo3::class::PyClassAttributeDef {
name: stringify!(#python_name),
meth: __wrap,
}
})
}
}
pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.python_name;
quote! {
pyo3::class::PyMethodDefType::ClassAttribute({

View File

@ -16,6 +16,9 @@ struct Bar {
#[pymethods]
impl Foo {
#[classattr]
const MY_CONST: &'static str = "foobar";
#[classattr]
fn a() -> i32 {
5
@ -53,6 +56,7 @@ fn class_attributes() {
let foo_obj = py.get_type::<Foo>();
py_assert!(py, foo_obj, "foo_obj.a == 5");
py_assert!(py, foo_obj, "foo_obj.B == 'bar'");
py_assert!(py, foo_obj, "foo_obj.MY_CONST == 'foobar'");
}
#[test]