Implement get/set all on pyclass
This commit is contained in:
parent
c9b26f57cd
commit
d254134154
|
@ -7,10 +7,12 @@
|
||||||
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
|
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
|
||||||
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
|
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
|
||||||
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrowchecker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |
|
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrowchecker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |
|
||||||
|
| `get_all` | Generates getters for all fields of the pyclass. |
|
||||||
| `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. |
|
| `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. |
|
||||||
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
|
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
|
||||||
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
|
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
|
||||||
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
|
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
|
||||||
|
| `set_all` | Generates setters for all fields of the pyclass. |
|
||||||
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
|
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
|
||||||
| <span style="white-space: pre">`text_signature = "(arg1, arg2, ...)"`</span> | Sets the text signature for the Python class' `__new__` method. |
|
| <span style="white-space: pre">`text_signature = "(arg1, arg2, ...)"`</span> | Sets the text signature for the Python class' `__new__` method. |
|
||||||
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread.|
|
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread.|
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub mod kw {
|
||||||
syn::custom_keyword!(frozen);
|
syn::custom_keyword!(frozen);
|
||||||
syn::custom_keyword!(gc);
|
syn::custom_keyword!(gc);
|
||||||
syn::custom_keyword!(get);
|
syn::custom_keyword!(get);
|
||||||
|
syn::custom_keyword!(get_all);
|
||||||
syn::custom_keyword!(item);
|
syn::custom_keyword!(item);
|
||||||
syn::custom_keyword!(mapping);
|
syn::custom_keyword!(mapping);
|
||||||
syn::custom_keyword!(module);
|
syn::custom_keyword!(module);
|
||||||
|
@ -25,6 +26,7 @@ pub mod kw {
|
||||||
syn::custom_keyword!(pass_module);
|
syn::custom_keyword!(pass_module);
|
||||||
syn::custom_keyword!(sequence);
|
syn::custom_keyword!(sequence);
|
||||||
syn::custom_keyword!(set);
|
syn::custom_keyword!(set);
|
||||||
|
syn::custom_keyword!(set_all);
|
||||||
syn::custom_keyword!(signature);
|
syn::custom_keyword!(signature);
|
||||||
syn::custom_keyword!(subclass);
|
syn::custom_keyword!(subclass);
|
||||||
syn::custom_keyword!(text_signature);
|
syn::custom_keyword!(text_signature);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
use crate::attributes::{
|
use crate::attributes::{
|
||||||
self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
|
self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
|
||||||
|
@ -60,12 +61,14 @@ pub struct PyClassPyO3Options {
|
||||||
pub krate: Option<CrateAttribute>,
|
pub krate: Option<CrateAttribute>,
|
||||||
pub dict: Option<kw::dict>,
|
pub dict: Option<kw::dict>,
|
||||||
pub extends: Option<ExtendsAttribute>,
|
pub extends: Option<ExtendsAttribute>,
|
||||||
|
pub get_all: Option<kw::get_all>,
|
||||||
pub freelist: Option<FreelistAttribute>,
|
pub freelist: Option<FreelistAttribute>,
|
||||||
pub frozen: Option<kw::frozen>,
|
pub frozen: Option<kw::frozen>,
|
||||||
pub mapping: Option<kw::mapping>,
|
pub mapping: Option<kw::mapping>,
|
||||||
pub module: Option<ModuleAttribute>,
|
pub module: Option<ModuleAttribute>,
|
||||||
pub name: Option<NameAttribute>,
|
pub name: Option<NameAttribute>,
|
||||||
pub sequence: Option<kw::sequence>,
|
pub sequence: Option<kw::sequence>,
|
||||||
|
pub set_all: Option<kw::set_all>,
|
||||||
pub subclass: Option<kw::subclass>,
|
pub subclass: Option<kw::subclass>,
|
||||||
pub text_signature: Option<TextSignatureAttribute>,
|
pub text_signature: Option<TextSignatureAttribute>,
|
||||||
pub unsendable: Option<kw::unsendable>,
|
pub unsendable: Option<kw::unsendable>,
|
||||||
|
@ -80,10 +83,12 @@ enum PyClassPyO3Option {
|
||||||
Extends(ExtendsAttribute),
|
Extends(ExtendsAttribute),
|
||||||
Freelist(FreelistAttribute),
|
Freelist(FreelistAttribute),
|
||||||
Frozen(kw::frozen),
|
Frozen(kw::frozen),
|
||||||
|
GetAll(kw::get_all),
|
||||||
Mapping(kw::mapping),
|
Mapping(kw::mapping),
|
||||||
Module(ModuleAttribute),
|
Module(ModuleAttribute),
|
||||||
Name(NameAttribute),
|
Name(NameAttribute),
|
||||||
Sequence(kw::sequence),
|
Sequence(kw::sequence),
|
||||||
|
SetAll(kw::set_all),
|
||||||
Subclass(kw::subclass),
|
Subclass(kw::subclass),
|
||||||
TextSignature(TextSignatureAttribute),
|
TextSignature(TextSignatureAttribute),
|
||||||
Unsendable(kw::unsendable),
|
Unsendable(kw::unsendable),
|
||||||
|
@ -105,6 +110,8 @@ impl Parse for PyClassPyO3Option {
|
||||||
input.parse().map(PyClassPyO3Option::Freelist)
|
input.parse().map(PyClassPyO3Option::Freelist)
|
||||||
} else if lookahead.peek(attributes::kw::frozen) {
|
} else if lookahead.peek(attributes::kw::frozen) {
|
||||||
input.parse().map(PyClassPyO3Option::Frozen)
|
input.parse().map(PyClassPyO3Option::Frozen)
|
||||||
|
} else if lookahead.peek(attributes::kw::get_all) {
|
||||||
|
input.parse().map(PyClassPyO3Option::GetAll)
|
||||||
} else if lookahead.peek(attributes::kw::mapping) {
|
} else if lookahead.peek(attributes::kw::mapping) {
|
||||||
input.parse().map(PyClassPyO3Option::Mapping)
|
input.parse().map(PyClassPyO3Option::Mapping)
|
||||||
} else if lookahead.peek(attributes::kw::module) {
|
} else if lookahead.peek(attributes::kw::module) {
|
||||||
|
@ -113,6 +120,8 @@ impl Parse for PyClassPyO3Option {
|
||||||
input.parse().map(PyClassPyO3Option::Name)
|
input.parse().map(PyClassPyO3Option::Name)
|
||||||
} else if lookahead.peek(attributes::kw::sequence) {
|
} else if lookahead.peek(attributes::kw::sequence) {
|
||||||
input.parse().map(PyClassPyO3Option::Sequence)
|
input.parse().map(PyClassPyO3Option::Sequence)
|
||||||
|
} else if lookahead.peek(attributes::kw::set_all) {
|
||||||
|
input.parse().map(PyClassPyO3Option::SetAll)
|
||||||
} else if lookahead.peek(attributes::kw::subclass) {
|
} else if lookahead.peek(attributes::kw::subclass) {
|
||||||
input.parse().map(PyClassPyO3Option::Subclass)
|
input.parse().map(PyClassPyO3Option::Subclass)
|
||||||
} else if lookahead.peek(attributes::kw::text_signature) {
|
} else if lookahead.peek(attributes::kw::text_signature) {
|
||||||
|
@ -165,10 +174,12 @@ impl PyClassPyO3Options {
|
||||||
PyClassPyO3Option::Extends(extends) => set_option!(extends),
|
PyClassPyO3Option::Extends(extends) => set_option!(extends),
|
||||||
PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
|
PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
|
||||||
PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
|
PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
|
||||||
|
PyClassPyO3Option::GetAll(get_all) => set_option!(get_all),
|
||||||
PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
|
PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
|
||||||
PyClassPyO3Option::Module(module) => set_option!(module),
|
PyClassPyO3Option::Module(module) => set_option!(module),
|
||||||
PyClassPyO3Option::Name(name) => set_option!(name),
|
PyClassPyO3Option::Name(name) => set_option!(name),
|
||||||
PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
|
PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
|
||||||
|
PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
|
||||||
PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
|
PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
|
||||||
PyClassPyO3Option::TextSignature(text_signature) => set_option!(text_signature),
|
PyClassPyO3Option::TextSignature(text_signature) => set_option!(text_signature),
|
||||||
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
|
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
|
||||||
|
@ -212,7 +223,7 @@ pub fn build_py_class(
|
||||||
For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters"
|
For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters"
|
||||||
);
|
);
|
||||||
|
|
||||||
let field_options = match &mut class.fields {
|
let mut field_options: Vec<(&syn::Field, FieldPyO3Options)> = match &mut class.fields {
|
||||||
syn::Fields::Named(fields) => fields
|
syn::Fields::Named(fields) => fields
|
||||||
.named
|
.named
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -230,11 +241,33 @@ pub fn build_py_class(
|
||||||
})
|
})
|
||||||
.collect::<Result<_>>()?,
|
.collect::<Result<_>>()?,
|
||||||
syn::Fields::Unit => {
|
syn::Fields::Unit => {
|
||||||
|
if let Some(attr) = args.options.set_all {
|
||||||
|
return Err(syn::Error::new(attr.span(), UNIT_SET));
|
||||||
|
};
|
||||||
|
if let Some(attr) = args.options.get_all {
|
||||||
|
return Err(syn::Error::new(attr.span(), UNIT_GET));
|
||||||
|
};
|
||||||
// No fields for unit struct
|
// No fields for unit struct
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(attr) = args.options.get_all {
|
||||||
|
for (_, FieldPyO3Options { get, .. }) in &mut field_options {
|
||||||
|
if mem::replace(get, true) {
|
||||||
|
return Err(syn::Error::new(attr.span(), DUPE_GET));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(attr) = args.options.set_all {
|
||||||
|
for (_, FieldPyO3Options { set, .. }) in &mut field_options {
|
||||||
|
if mem::replace(set, true) {
|
||||||
|
return Err(syn::Error::new(attr.span(), DUPE_SET));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl_class(&class.ident, &args, doc, field_options, methods_type, krate)
|
impl_class(&class.ident, &args, doc, field_options, methods_type, krate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1085,3 +1118,10 @@ fn define_inventory_class(inventory_class_name: &syn::Ident) -> TokenStream {
|
||||||
_pyo3::inventory::collect!(#inventory_class_name);
|
_pyo3::inventory::collect!(#inventory_class_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DUPE_SET: &str = "duplicate `set` - the struct is already annotated with `set_all`";
|
||||||
|
const DUPE_GET: &str = "duplicate `get` - the struct is already annotated with `get_all`";
|
||||||
|
const UNIT_GET: &str =
|
||||||
|
"`get_all` on an unit struct does nothing, because unit structs have no fields";
|
||||||
|
const UNIT_SET: &str =
|
||||||
|
"`set_all` on an unit struct does nothing, because unit structs have no fields";
|
||||||
|
|
|
@ -117,6 +117,7 @@ fn _test_compile_errors() {
|
||||||
t.compile_fail("tests/ui/not_send.rs");
|
t.compile_fail("tests/ui/not_send.rs");
|
||||||
t.compile_fail("tests/ui/not_send2.rs");
|
t.compile_fail("tests/ui/not_send2.rs");
|
||||||
t.compile_fail("tests/ui/not_send3.rs");
|
t.compile_fail("tests/ui/not_send3.rs");
|
||||||
|
t.compile_fail("tests/ui/get_set_all.rs");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustversion::before(1.63)]
|
#[rustversion::before(1.63)]
|
||||||
|
|
|
@ -158,3 +158,34 @@ fn tuple_struct_getter_setter() {
|
||||||
py_assert!(py, inst, "inst.num == 20");
|
py_assert!(py, inst, "inst.num == 20");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all, set_all)]
|
||||||
|
struct All {
|
||||||
|
num: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_set_all() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let inst = Py::new(py, All { num: 10 }).unwrap();
|
||||||
|
|
||||||
|
py_run!(py, inst, "assert inst.num == 10");
|
||||||
|
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
struct All2 {
|
||||||
|
#[pyo3(set)]
|
||||||
|
num: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_all_and_set() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let inst = Py::new(py, All2 { num: 10 }).unwrap();
|
||||||
|
|
||||||
|
py_run!(py, inst, "assert inst.num == 10");
|
||||||
|
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
21
tests/ui/get_set_all.rs
Normal file
21
tests/ui/get_set_all.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
#[pyclass(set_all)]
|
||||||
|
struct Foo;
|
||||||
|
|
||||||
|
#[pyclass(set_all)]
|
||||||
|
struct Foo2{
|
||||||
|
#[pyo3(set)]
|
||||||
|
field: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
struct Foo3;
|
||||||
|
|
||||||
|
#[pyclass(get_all)]
|
||||||
|
struct Foo4{
|
||||||
|
#[pyo3(get)]
|
||||||
|
field: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
23
tests/ui/get_set_all.stderr
Normal file
23
tests/ui/get_set_all.stderr
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
error: `set_all` on an unit struct does nothing, because unit structs have no fields
|
||||||
|
--> tests/ui/get_set_all.rs:3:11
|
||||||
|
|
|
||||||
|
3 | #[pyclass(set_all)]
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: duplicate `set` - the struct is already annotated with `set_all`
|
||||||
|
--> tests/ui/get_set_all.rs:6:11
|
||||||
|
|
|
||||||
|
6 | #[pyclass(set_all)]
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: `get_all` on an unit struct does nothing, because unit structs have no fields
|
||||||
|
--> tests/ui/get_set_all.rs:12:11
|
||||||
|
|
|
||||||
|
12 | #[pyclass(get_all)]
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: duplicate `get` - the struct is already annotated with `get_all`
|
||||||
|
--> tests/ui/get_set_all.rs:15:11
|
||||||
|
|
|
||||||
|
15 | #[pyclass(get_all)]
|
||||||
|
| ^^^^^^^
|
|
@ -1,4 +1,4 @@
|
||||||
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `mapping`, `module`, `name`, `sequence`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `sequence`, `set_all`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
||||||
--> tests/ui/invalid_pyclass_args.rs:3:11
|
--> tests/ui/invalid_pyclass_args.rs:3:11
|
||||||
|
|
|
|
||||||
3 | #[pyclass(extend=pyo3::types::PyDict)]
|
3 | #[pyclass(extend=pyo3::types::PyDict)]
|
||||||
|
@ -34,7 +34,7 @@ error: expected string literal
|
||||||
18 | #[pyclass(module = my_module)]
|
18 | #[pyclass(module = my_module)]
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
||||||
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `mapping`, `module`, `name`, `sequence`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `sequence`, `set_all`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
||||||
--> tests/ui/invalid_pyclass_args.rs:21:11
|
--> tests/ui/invalid_pyclass_args.rs:21:11
|
||||||
|
|
|
|
||||||
21 | #[pyclass(weakrev)]
|
21 | #[pyclass(weakrev)]
|
||||||
|
|
Loading…
Reference in a new issue