Generate getters/setters (#76)
This commit is contained in:
parent
11f244692e
commit
268d7fd3d2
|
@ -189,6 +189,18 @@ impl MyClass {
|
||||||
|
|
||||||
In this case property `number` is defined. And it is available from python code as `self.number`.
|
In this case property `number` is defined. And it is available from python code as `self.number`.
|
||||||
|
|
||||||
|
For simple cases you can also define getters and setters in your Rust struct field definition, for example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[py:class]
|
||||||
|
struct MyClass {
|
||||||
|
#[prop(get, set)]
|
||||||
|
num: i32
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then it is available from Python code as `self.num`.
|
||||||
|
|
||||||
## Instance methods
|
## Instance methods
|
||||||
|
|
||||||
To define python compatible method, `impl` block for struct has to be annotated
|
To define python compatible method, `impl` block for struct has to be annotated
|
||||||
|
|
|
@ -7,19 +7,26 @@ use syn;
|
||||||
use quote::{Tokens, ToTokens};
|
use quote::{Tokens, ToTokens};
|
||||||
|
|
||||||
use utils;
|
use utils;
|
||||||
|
use method::{FnType, FnSpec, FnArg};
|
||||||
|
use py_method::{impl_wrap_getter, impl_wrap_setter, impl_py_getter_def, impl_py_setter_def};
|
||||||
|
|
||||||
|
|
||||||
pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
|
pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
|
||||||
let (params, flags, base) = parse_attribute(attr);
|
let (params, flags, base) = parse_attribute(attr);
|
||||||
let doc = utils::get_doc(&ast.attrs, true);
|
let doc = utils::get_doc(&ast.attrs, true);
|
||||||
let mut token: Option<syn::Ident> = None;
|
let mut token: Option<syn::Ident> = None;
|
||||||
|
let mut descriptors = Vec::new();
|
||||||
match ast.body {
|
match ast.body {
|
||||||
syn::Body::Struct(syn::VariantData::Struct(ref mut fields)) => {
|
syn::Body::Struct(syn::VariantData::Struct(ref mut fields)) => {
|
||||||
for field in fields.iter() {
|
for field in fields.iter_mut() {
|
||||||
if is_python_token(field) {
|
if is_python_token(field) {
|
||||||
token = field.ident.clone();
|
token = field.ident.clone();
|
||||||
break
|
break
|
||||||
|
} else {
|
||||||
|
let field_descs = parse_descriptors(field);
|
||||||
|
if !field_descs.is_empty() {
|
||||||
|
descriptors.push((field.clone(), field_descs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -27,7 +34,7 @@ pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
|
||||||
}
|
}
|
||||||
|
|
||||||
let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_CLS_{}", ast.ident));
|
let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_CLS_{}", ast.ident));
|
||||||
let tokens = impl_class(&ast.ident, &base, token, doc, params, flags);
|
let tokens = impl_class(&ast.ident, &base, token, doc, params, flags, descriptors);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[allow(non_upper_case_globals, unused_attributes,
|
#[allow(non_upper_case_globals, unused_attributes,
|
||||||
|
@ -41,9 +48,53 @@ pub fn build_py_class(ast: &mut syn::DeriveInput, attr: String) -> Tokens {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_descriptors(item: &mut syn::Field) -> Vec<FnType> {
|
||||||
|
let mut descs = Vec::new();
|
||||||
|
let mut new_attrs = Vec::new();
|
||||||
|
for attr in item.attrs.iter() {
|
||||||
|
match attr.value {
|
||||||
|
syn::MetaItem::List(ref name, ref metas) => {
|
||||||
|
match name.as_ref() {
|
||||||
|
"prop" => {
|
||||||
|
for meta in metas.iter() {
|
||||||
|
match *meta {
|
||||||
|
syn::NestedMetaItem::MetaItem(ref metaitem) => {
|
||||||
|
match metaitem.name() {
|
||||||
|
"get" => {
|
||||||
|
descs.push(FnType::Getter(None));
|
||||||
|
}
|
||||||
|
"set" => {
|
||||||
|
descs.push(FnType::Setter(None));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Only getter and setter supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
new_attrs.push(attr.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
new_attrs.push(attr.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.attrs.clear();
|
||||||
|
item.attrs.extend(new_attrs);
|
||||||
|
descs
|
||||||
|
}
|
||||||
|
|
||||||
fn impl_class(cls: &syn::Ident, base: &syn::Ident,
|
fn impl_class(cls: &syn::Ident, base: &syn::Ident,
|
||||||
token: Option<syn::Ident>, doc: syn::Lit,
|
token: Option<syn::Ident>, doc: syn::Lit,
|
||||||
params: HashMap<&'static str, syn::Ident>, flags: Vec<syn::Ident>) -> Tokens {
|
params: HashMap<&'static str, syn::Ident>,
|
||||||
|
flags: Vec<syn::Ident>,
|
||||||
|
descriptors: Vec<(syn::Field, Vec<FnType>)>) -> Tokens {
|
||||||
let cls_name = match params.get("name") {
|
let cls_name = match params.get("name") {
|
||||||
Some(name) => quote! { #name }.as_str().to_string(),
|
Some(name) => quote! { #name }.as_str().to_string(),
|
||||||
None => quote! { #cls }.as_str().to_string()
|
None => quote! { #cls }.as_str().to_string()
|
||||||
|
@ -145,6 +196,17 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let extra = if !descriptors.is_empty() {
|
||||||
|
let ty = syn::parse::ty(cls.as_ref()).expect("no name");
|
||||||
|
let desc_impls = impl_descriptors(&ty, descriptors);
|
||||||
|
Some(quote! {
|
||||||
|
#desc_impls
|
||||||
|
#extra
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
extra
|
||||||
|
};
|
||||||
|
|
||||||
// insert space for weak ref
|
// insert space for weak ref
|
||||||
let mut has_weakref = false;
|
let mut has_weakref = false;
|
||||||
let mut has_dict = false;
|
let mut has_dict = false;
|
||||||
|
@ -219,6 +281,103 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn impl_descriptors(cls: &syn::Ty, descriptors: Vec<(syn::Field, Vec<FnType>)>) -> Tokens {
|
||||||
|
let methods: Vec<Tokens> = descriptors.iter().flat_map(|&(ref field, ref fns)| {
|
||||||
|
fns.iter().map(|desc| {
|
||||||
|
let name = field.ident.clone().unwrap();
|
||||||
|
let field_ty = &field.ty;
|
||||||
|
match *desc {
|
||||||
|
FnType::Getter(_) => {
|
||||||
|
quote! {
|
||||||
|
impl #cls {
|
||||||
|
fn #name(&self) -> _pyo3::PyResult<#field_ty> {
|
||||||
|
Ok(self.#name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FnType::Setter(_) => {
|
||||||
|
let setter_name = syn::Ident::from(format!("set_{}", name));
|
||||||
|
quote! {
|
||||||
|
impl #cls {
|
||||||
|
fn #setter_name(&mut self, value: #field_ty) -> _pyo3::PyResult<()> {
|
||||||
|
self.#name = value;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}).collect::<Vec<Tokens>>()
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let py_methods: Vec<Tokens> = descriptors.iter().flat_map(|&(ref field, ref fns)| {
|
||||||
|
fns.iter().map(|desc| {
|
||||||
|
let name = field.ident.clone().unwrap();
|
||||||
|
// FIXME better doc?
|
||||||
|
let doc = syn::Lit::from(name.as_ref());
|
||||||
|
let field_ty = &field.ty;
|
||||||
|
match *desc {
|
||||||
|
FnType::Getter(ref getter) => {
|
||||||
|
impl_py_getter_def(&name, doc, getter, &impl_wrap_getter(&Box::new(cls.clone()), &name))
|
||||||
|
}
|
||||||
|
FnType::Setter(ref setter) => {
|
||||||
|
let mode = syn::BindingMode::ByValue(syn::Mutability::Immutable);
|
||||||
|
let setter_name = syn::Ident::from(format!("set_{}", name));
|
||||||
|
let spec = FnSpec {
|
||||||
|
tp: FnType::Setter(None),
|
||||||
|
attrs: Vec::new(),
|
||||||
|
args: vec![FnArg {
|
||||||
|
name: &name,
|
||||||
|
mode: &mode,
|
||||||
|
ty: field_ty,
|
||||||
|
optional: None,
|
||||||
|
py: true,
|
||||||
|
reference: false
|
||||||
|
}],
|
||||||
|
output: syn::parse::ty("PyResult<()>").expect("error parse PyResult<()>")
|
||||||
|
};
|
||||||
|
impl_py_setter_def(&name, doc, setter, &impl_wrap_setter(&Box::new(cls.clone()), &setter_name, &spec))
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}).collect::<Vec<Tokens>>()
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let tokens = quote! {
|
||||||
|
#(#methods)*
|
||||||
|
|
||||||
|
impl _pyo3::class::methods::PyMethodsProtocolImpl for #cls {
|
||||||
|
fn py_methods() -> &'static [_pyo3::class::PyMethodDefType] {
|
||||||
|
static METHODS: &'static [_pyo3::class::PyMethodDefType] = &[
|
||||||
|
#(#py_methods),*
|
||||||
|
];
|
||||||
|
METHODS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = match cls {
|
||||||
|
&syn::Ty::Path(_, ref p) => {
|
||||||
|
p.segments.last().as_ref().unwrap().ident.as_ref()
|
||||||
|
}
|
||||||
|
_ => "CLS_METHODS"
|
||||||
|
};
|
||||||
|
|
||||||
|
let dummy_const = syn::Ident::new(format!("_IMPL_PYO3_DESCRIPTORS_{}", n));
|
||||||
|
quote! {
|
||||||
|
#[feature(specialization)]
|
||||||
|
#[allow(non_upper_case_globals, unused_attributes,
|
||||||
|
unused_qualifications, unused_variables, unused_imports)]
|
||||||
|
const #dummy_const: () = {
|
||||||
|
extern crate pyo3 as _pyo3;
|
||||||
|
|
||||||
|
#tokens
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_python_token(field: &syn::Field) -> bool {
|
fn is_python_token(field: &syn::Field) -> bool {
|
||||||
match field.ty {
|
match field.ty {
|
||||||
syn::Ty::Path(_, ref path) => {
|
syn::Ty::Path(_, ref path) => {
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub fn gen_py_method<'a>(cls: &Box<syn::Ty>, name: &syn::Ident,
|
||||||
FnType::FnStatic =>
|
FnType::FnStatic =>
|
||||||
impl_py_method_def_static(name, doc, &impl_wrap_static(cls, name, &spec)),
|
impl_py_method_def_static(name, doc, &impl_wrap_static(cls, name, &spec)),
|
||||||
FnType::Getter(ref getter) =>
|
FnType::Getter(ref getter) =>
|
||||||
impl_py_getter_def(name, doc, getter, &impl_wrap_getter(cls, name, &spec)),
|
impl_py_getter_def(name, doc, getter, &impl_wrap_getter(cls, name)),
|
||||||
FnType::Setter(ref setter) =>
|
FnType::Setter(ref setter) =>
|
||||||
impl_py_setter_def(name, doc, setter, &impl_wrap_setter(cls, name, &spec)),
|
impl_py_setter_def(name, doc, setter, &impl_wrap_setter(cls, name, &spec)),
|
||||||
}
|
}
|
||||||
|
@ -259,7 +259,7 @@ pub fn impl_wrap_static(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords)
|
/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||||
fn impl_wrap_getter(cls: &Box<syn::Ty>, name: &syn::Ident, _spec: &FnSpec) -> Tokens {
|
pub(crate) fn impl_wrap_getter(cls: &Box<syn::Ty>, name: &syn::Ident) -> Tokens {
|
||||||
quote! {
|
quote! {
|
||||||
unsafe extern "C" fn __wrap(
|
unsafe extern "C" fn __wrap(
|
||||||
_slf: *mut _pyo3::ffi::PyObject, _: *mut _pyo3::c_void) -> *mut _pyo3::ffi::PyObject
|
_slf: *mut _pyo3::ffi::PyObject, _: *mut _pyo3::c_void) -> *mut _pyo3::ffi::PyObject
|
||||||
|
@ -285,7 +285,7 @@ fn impl_wrap_getter(cls: &Box<syn::Ty>, name: &syn::Ident, _spec: &FnSpec) -> To
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords)
|
/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords)
|
||||||
fn impl_wrap_setter(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens {
|
pub(crate) fn impl_wrap_setter(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens {
|
||||||
if spec.args.len() < 1 {
|
if spec.args.len() < 1 {
|
||||||
println!("Not enough arguments for setter {}::{}", quote!{#cls}, name);
|
println!("Not enough arguments for setter {}::{}", quote!{#cls}, name);
|
||||||
}
|
}
|
||||||
|
@ -678,8 +678,8 @@ pub fn impl_py_method_def_call(name: &syn::Ident, doc: syn::Lit, wrapper: &Token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_py_setter_def(name: &syn::Ident, doc: syn::Lit, setter: &Option<String>, wrapper: &Tokens)
|
pub(crate) fn impl_py_setter_def(name: &syn::Ident, doc: syn::Lit, setter: &Option<String>, wrapper: &Tokens)
|
||||||
-> Tokens
|
-> Tokens
|
||||||
{
|
{
|
||||||
let n = if let &Some(ref name) = setter {
|
let n = if let &Some(ref name) = setter {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
|
@ -705,8 +705,8 @@ fn impl_py_setter_def(name: &syn::Ident, doc: syn::Lit, setter: &Option<String>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_py_getter_def(name: &syn::Ident, doc: syn::Lit, getter: &Option<String>, wrapper: &Tokens)
|
pub(crate) fn impl_py_getter_def(name: &syn::Ident, doc: syn::Lit, getter: &Option<String>, wrapper: &Tokens)
|
||||||
-> Tokens
|
-> Tokens
|
||||||
{
|
{
|
||||||
let n = if let &Some(ref name) = getter {
|
let n = if let &Some(ref name) = getter {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
|
|
|
@ -1280,3 +1280,21 @@ fn weakref_dunder_dict_support() {
|
||||||
let inst = Py::new_ref(py, |t| WeakRefDunderDictSupport{token: t}).unwrap();
|
let inst = Py::new_ref(py, |t| WeakRefDunderDictSupport{token: t}).unwrap();
|
||||||
py_run!(py, inst, "import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1");
|
py_run!(py, inst, "import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[py::class]
|
||||||
|
struct GetterSetter {
|
||||||
|
#[prop(get, set)]
|
||||||
|
num: i32,
|
||||||
|
token: PyToken
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn getter_setter_autogen() {
|
||||||
|
let gil = Python::acquire_gil();
|
||||||
|
let py = gil.python();
|
||||||
|
|
||||||
|
let inst = py.init(|t| GetterSetter{num: 10, token: t}).unwrap();
|
||||||
|
|
||||||
|
py_run!(py, inst, "assert inst.num == 10");
|
||||||
|
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue