Merge pull request #1504 from PyO3/pyclass

Add tuple and unit struct support for pyclass macro
This commit is contained in:
Yuji Kanagawa 2021-03-20 16:14:15 +09:00 committed by GitHub
commit acff3b1f3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 140 additions and 18 deletions

View file

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add FFI definition `PyCFunction_CheckExact` for Python 3.9 and later. [#1425](https://github.com/PyO3/pyo3/pull/1425)
- Add FFI definition `Py_IS_TYPE`. [#1429](https://github.com/PyO3/pyo3/pull/1429)
- Add FFI definition `_Py_InitializeMain`. [#1473](https://github.com/PyO3/pyo3/pull/1473)
- Add tuple and unit struct support for `#[pyclass]` macro. [#1504](https://github.com/PyO3/pyo3/pull/1504)
### Changed
- Change `PyTimeAcces::get_fold()` to return a `bool` instead of a `u8`. [#1397](https://github.com/PyO3/pyo3/pull/1397)

View file

@ -179,15 +179,24 @@ pub fn build_py_class(
class.generics.span() => "#[pyclass] cannot have generic parameters"
);
if let syn::Fields::Named(fields) = &mut class.fields {
for field in fields.named.iter_mut() {
let field_descs = parse_descriptors(field)?;
if !field_descs.is_empty() {
descriptors.push((field.clone(), field_descs));
match &mut class.fields {
syn::Fields::Named(fields) => {
for field in fields.named.iter_mut() {
let field_descs = parse_descriptors(field)?;
if !field_descs.is_empty() {
descriptors.push((field.clone(), field_descs));
}
}
}
} else {
bail_spanned!(class.fields.span() => "#[pyclass] can only be used with C-style structs");
syn::Fields::Unnamed(fields) => {
for field in fields.unnamed.iter_mut() {
let field_descs = parse_descriptors(field)?;
if !field_descs.is_empty() {
descriptors.push((field.clone(), field_descs));
}
}
}
syn::Fields::Unit => { /* No fields for unit struct */ }
}
impl_class(&class.ident, &attr, doc, descriptors, methods_type)
@ -497,18 +506,21 @@ fn impl_descriptors(
.flat_map(|(field, fns)| {
fns.iter()
.map(|desc| {
let name = field.ident.as_ref().unwrap().unraw();
let doc = utils::get_doc(&field.attrs, None, true)
.unwrap_or_else(|_| syn::LitStr::new(&name.to_string(), name.span()));
let property_type = PropertyType::Descriptor(&field);
match desc {
FnType::Getter(self_ty) => {
impl_py_getter_def(cls, property_type, self_ty, &name, &doc)
if let Some(name) = field.ident.as_ref().map(|ident| ident.unraw()) {
let doc = utils::get_doc(&field.attrs, None, true)
.unwrap_or_else(|_| syn::LitStr::new(&name.to_string(), name.span()));
let property_type = PropertyType::Descriptor(&field);
match desc {
FnType::Getter(self_ty) => {
impl_py_getter_def(cls, property_type, self_ty, &name, &doc)
}
FnType::Setter(self_ty) => {
impl_py_setter_def(cls, property_type, self_ty, &name, &doc)
}
_ => unreachable!(),
}
FnType::Setter(self_ty) => {
impl_py_setter_def(cls, property_type, self_ty, &name, &doc)
}
_ => unreachable!(),
} else {
bail_spanned!(field.span() => "get/set are not supported on tuple struct field");
}
})
.collect::<Vec<syn::Result<TokenStream>>>()

View file

@ -18,6 +18,20 @@ fn empty_class() {
py_assert!(py, typeobj, "typeobj.__name__ == 'EmptyClass'");
}
#[pyclass]
struct UnitClass;
#[test]
fn unit_class() {
Python::with_gil(|py| {
let typeobj = py.get_type::<UnitClass>();
// By default, don't allow creating instances from python.
assert!(typeobj.call((), None).is_err());
py_assert!(py, typeobj, "typeobj.__name__ == 'UnitClass'");
});
}
/// Line1
///Line2
/// Line3
@ -289,3 +303,16 @@ fn test_pymethods_from_py_with() {
);
})
}
#[pyclass]
struct TupleClass(i32);
#[test]
fn test_tuple_struct_class() {
Python::with_gil(|py| {
let typeobj = py.get_type::<TupleClass>();
assert!(typeobj.call((), None).is_err());
py_assert!(py, typeobj, "typeobj.__name__ == 'TupleClass'");
});
}

View file

@ -24,6 +24,51 @@ fn empty_class_with_new() {
.is_ok());
}
#[pyclass]
struct UnitClassWithNew;
#[pymethods]
impl UnitClassWithNew {
#[new]
fn new() -> Self {
Self
}
}
#[test]
fn unit_class_with_new() {
Python::with_gil(|py| {
let typeobj = py.get_type::<UnitClassWithNew>();
assert!(typeobj
.call((), None)
.unwrap()
.cast_as::<PyCell<UnitClassWithNew>>()
.is_ok());
});
}
#[pyclass]
struct TupleClassWithNew(i32);
#[pymethods]
impl TupleClassWithNew {
#[new]
fn new(arg: i32) -> Self {
Self(arg)
}
}
#[test]
fn tuple_class_with_new() {
Python::with_gil(|py| {
let typeobj = py.get_type::<TupleClassWithNew>();
let wrp = typeobj.call((42,), None).unwrap();
let obj = wrp.cast_as::<PyCell<TupleClassWithNew>>().unwrap();
let obj_ref = obj.borrow();
assert_eq!(obj_ref.0, 42);
});
}
#[pyclass]
#[derive(Debug)]
struct NewWithOneArg {

View file

@ -130,3 +130,31 @@ fn ref_getter_setter() {
py_run!(py, inst, "assert inst.num == 10");
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
}
#[pyclass]
struct TupleClassGetterSetter(i32);
#[pymethods]
impl TupleClassGetterSetter {
#[getter(num)]
fn get_num(&self) -> i32 {
self.0
}
#[setter(num)]
fn set_num(&mut self, value: i32) {
self.0 = value;
}
}
#[test]
fn tuple_struct_getter_setter() {
let gil = Python::acquire_gil();
let py = gil.python();
let inst = Py::new(py, TupleClassGetterSetter(10)).unwrap();
py_assert!(py, inst, "inst.num == 10");
py_run!(py, inst, "inst.num = 20");
py_assert!(py, inst, "inst.num == 20");
}

View file

@ -24,4 +24,7 @@ impl ClassWithSetter {
fn setter_with_too_many_args(&mut self, py: Python, foo: u32, bar: u32) {}
}
#[pyclass]
struct TupleGetterSetter(#[pyo3(get, set)] i32);
fn main() {}

View file

@ -15,3 +15,9 @@ error: setter function can have at most two arguments ([pyo3::Python,] and value
|
24 | fn setter_with_too_many_args(&mut self, py: Python, foo: u32, bar: u32) {}
| ^^^
error: get/set are not supported on tuple struct field
--> $DIR/invalid_property_args.rs:28:44
|
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);
| ^^^