Skip to content

Commit

Permalink
Merge pull request #208 from markbt/properties
Browse files Browse the repository at this point in the history
Implement property support
  • Loading branch information
markbt authored Feb 26, 2020
2 parents 77393d5 + 64cab6a commit 7fb4dd2
Show file tree
Hide file tree
Showing 10 changed files with 845 additions and 368 deletions.
6 changes: 1 addition & 5 deletions src/argparse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ pub struct ParamDescription<'a> {
impl<'a> ParamDescription<'a> {
/// Name, with leading `r#` stripped.
pub fn name(&self) -> &str {
if self.name.starts_with("r#") {
&self.name[2..]
} else {
self.name
}
crate::strip_raw!(self.name)
}
}

Expand Down
5 changes: 1 addition & 4 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,7 @@ macro_rules! py_method_def {
| $flags,
ml_doc: 0 as *const $crate::_detail::libc::c_char,
};
METHOD_DEF.ml_name = concat!($name, "\0").as_ptr() as *const _;
if $name.starts_with("r#") {
METHOD_DEF.ml_name = METHOD_DEF.ml_name.add(2);
}
METHOD_DEF.ml_name = $crate::strip_raw!(concat!($name, "\0")).as_ptr() as *const _;
if !$doc.is_empty() {
METHOD_DEF.ml_doc = concat!($doc, "\0").as_ptr() as *const _;
}
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,3 +393,17 @@ pub unsafe fn py_module_initializer_impl(
mem::forget(guard);
ret
}

// Strip 'r#' prefix from stringified raw identifiers.
#[macro_export]
#[doc(hidden)]
macro_rules! strip_raw {
($s:expr) => {{
let s = $s;
if s.starts_with("r#") {
&s[2..]
} else {
s
}
}};
}
7 changes: 1 addition & 6 deletions src/py_class/members.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,7 @@ macro_rules! py_class_init_members {
let descriptor = unsafe {
$crate::py_class::members::TypeMember::<$class>::into_descriptor(init, $py, &mut $type_object)
}?;
let name = stringify!($name);
let name = if name.starts_with("r#") {
&name[2..]
} else {
name
};
let name = $crate::strip_raw!(stringify!($name));
dict.set_item($py, name, descriptor)?;
})*
unsafe {
Expand Down
20 changes: 20 additions & 0 deletions src/py_class/py_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,24 @@ Declares a static method callable from Python.
* For details on `parameter-list`, see the documentation of `py_argparse!()`.
* The return type must be `PyResult<T>` for some `T` that implements `ToPyObject`.
## Properties
`@property def property_name(&self) -> PyResult<...> { ... }`
`@property_name.setter def set_method_name(&self, value: Option<impl FromPyObject>) -> PyResult<()> { ... }`
Declares a property (attribute with code for getting and optionally setting) accessible from Python.
* The setter is optional. If omitted, the attribute will be read-only.
* Unlike Python, the setter method name must be different from the property name.
The setter method name is used to call the setter from Rust.
* If value is `None` then the property is being deleted.
* The value type can be any type that implements `FromPyObject`, or a reference or
optional reference to any type that implements `RefFromPyObject`. In the latter
case, the type of the value is `Option<Option<&impl RefFromPyObject>>`, where
`None` means the property is being deleted, `Some(None)` means the property is
being set to Python `None`, and `Some(Some(value))` means the property is being
set to the given value.
## __new__
`def __new__(cls, parameter-list) -> PyResult<...> { ... }`
Expand Down Expand Up @@ -448,6 +466,7 @@ macro_rules! py_class {
}
/* impls: */ { /* impl body */ }
/* members: */ { /* ident = expr; */ }
/* props: */ { [ /* getters */ ] [ /* setters */ ] }
}
);
(pub class $class:ident |$py: ident| { $( $body:tt )* }) => (
Expand Down Expand Up @@ -477,6 +496,7 @@ macro_rules! py_class {
}
/* impls: */ { /* impl body */ }
/* members: */ { /* ident = expr; */ }
/* props: */ { [ /* getters */ ] [ /* setters */ ] }
}
);
}
Expand Down
42 changes: 39 additions & 3 deletions src/py_class/py_class_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
$gc:tt,
/* data: */ [ $( { $data_offset:expr, $data_name:ident, $data_ty:ty, $init_expr:expr, $init_ty:ty } )* ]
}
$slots:tt { $( $imp:item )* } $members:tt
$slots:tt { $( $imp:item )* } $members:tt $props:tt
} => {
$crate::py_coerce_item! {
$($class_visibility)* struct $class { _unsafe_inner: $crate::PyObject }
Expand Down Expand Up @@ -190,7 +190,7 @@
}
fn init($py: $crate::Python, module_name: Option<&str>) -> $crate::PyResult<$crate::PyType> {
$crate::py_class_type_object_dynamic_init!($class, $py, TYPE_OBJECT, module_name, $slots);
$crate::py_class_type_object_dynamic_init!($class, $py, TYPE_OBJECT, module_name, $slots $props);
$crate::py_class_init_members!($class, $py, TYPE_OBJECT, $members);
unsafe {
if $crate::_detail::ffi::PyType_Ready(&mut TYPE_OBJECT) == 0 {
Expand Down Expand Up @@ -250,7 +250,7 @@ def write(text):
('sdi', 'setdelitem', ['sdi_setitem', 'sdi_delitem'])
)

def generate_case(pattern, old_info=None, new_info=None, new_impl=None, new_slots=None, new_members=None):
def generate_case(pattern, old_info=None, new_info=None, new_impl=None, new_slots=None, new_members=None, new_props=None):
write('{ { %s $($tail:tt)* }\n' % pattern)
write('$class:ident $py:ident')
if old_info is not None:
Expand Down Expand Up @@ -293,6 +293,11 @@ def generate_case(pattern, old_info=None, new_info=None, new_impl=None, new_slot
write('\n{ $( $member_name:ident = $member_expr:expr; )* }')
else:
write('$members:tt')
if new_props:
write('\n{ [ $( $prop_doc:tt $prop_getter_name:ident: $prop_type:ty, )* ]')
write('\n[ $( $prop_setter_name:ident : $prop_setter_value_type:tt => $prop_setter_setter:ident, )* ] }')
else:
write('$props:tt')
write('\n} => { $crate::py_class_impl! {\n')
write('{ $($tail)* }\n')
write('$class $py')
Expand Down Expand Up @@ -335,6 +340,18 @@ def generate_case(pattern, old_info=None, new_info=None, new_impl=None, new_slot
write('}')
else:
write('$members')
if new_props:
getters, setters = new_props
write('\n/* props: */ {\n')
write('[ $( $prop_doc $prop_getter_name: $prop_type, )*\n')
for doc, name, ty in getters:
write('{ %s } %s: %s,\n' % (doc, name, ty))
write(']\n[ $( $prop_setter_name : $prop_setter_value_type => $prop_setter_setter, )*\n')
for name, ty, setter in setters:
write('%s : [ %s ] => %s,\n' % (name, ty, setter))
write(']\n}\n')
else:
write('$props')
write('\n}};\n')

def data_decl():
Expand Down Expand Up @@ -553,6 +570,24 @@ def static_data():
generate_case('static $name:ident = $init:expr;',
new_members=[('$name', '$init')])

def property_method():
generate_case('$(#[doc=$doc:expr])* @property def $name:ident(&$slf:ident) -> $res_type:ty { $( $body:tt )* }',
new_impl='$crate::py_class_impl_item! { $class, $py, $name(&$slf,) $res_type; { $($body)* } [] }',
new_props=([('concat!($($doc, "\\n"),*)', '$name', '$res_type')], [])
)
generate_case('@$name:ident.setter def $setter_name:ident(&$slf:ident, $value:ident : Option<Option<&$value_type:ty>> ) -> $res_type:ty { $( $body:tt )* }',
new_impl='$crate::py_class_impl_item! { $class, $py, $setter_name(&$slf,) $res_type; { $($body)* } [{ $value: Option<Option<&$value_type>> = {} }] }',
new_props=([], [('$name', 'Option<&$value_type>', '$setter_name')])
)
generate_case('@$name:ident.setter def $setter_name:ident(&$slf:ident, $value:ident : Option<&$value_type:ty> ) -> $res_type:ty { $( $body:tt )* }',
new_impl='$crate::py_class_impl_item! { $class, $py, $setter_name(&$slf,) $res_type; { $($body)* } [{ $value: Option<&$value_type> = {} }] }',
new_props=([], [('$name', '&$value_type', '$setter_name')])
)
generate_case('@$name:ident.setter def $setter_name:ident(&$slf:ident, $value:ident : Option<$value_type:ty> ) -> $res_type:ty { $( $body:tt )* }',
new_impl='$crate::py_class_impl_item! { $class, $py, $setter_name(&$slf,) $res_type; { $($body)* } [{ $value: Option<$value_type> = {} }] }',
new_props=([], [('$name', '$value_type', '$setter_name')])
)

macro_end = '''
}
'''
Expand Down Expand Up @@ -867,6 +902,7 @@ def main():
value_args='$py, $class::$name')
static_method()
static_data()
property_method()
print(macro_end)

if __name__ == '__main__':
Expand Down
Loading

0 comments on commit 7fb4dd2

Please sign in to comment.