pyclass: allow `#[pyo3(get, set, name = "foo")]`

This commit is contained in:
David Hewitt 2021-06-04 11:16:25 +01:00
parent 106cf25991
commit d011467e63
13 changed files with 327 additions and 167 deletions

View File

@ -349,7 +349,7 @@ struct MyClass {
}
```
The above would make the `num` property available for reading and writing from Python code as `self.num`.
The above would make the `num` field available for reading and writing as a `self.num` Python property. To expose the property with a different name to the field, specify this alongside the rest of the options, e.g. `#[pyo3(get, set, name = "custom_name")]`.
Properties can be readonly or writeonly by using just `#[pyo3(get)]` or `#[pyo3(set)]` respectively.

View File

@ -12,9 +12,11 @@ pub mod kw {
syn::custom_keyword!(annotation);
syn::custom_keyword!(attribute);
syn::custom_keyword!(from_py_with);
syn::custom_keyword!(get);
syn::custom_keyword!(item);
syn::custom_keyword!(pass_module);
syn::custom_keyword!(name);
syn::custom_keyword!(set);
syn::custom_keyword!(signature);
syn::custom_keyword!(transparent);
}
@ -43,9 +45,7 @@ impl Parse for NameAttribute {
}
}
pub fn get_pyo3_attributes<T: Parse>(
attr: &syn::Attribute,
) -> Result<Option<Punctuated<T, Comma>>> {
pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> {
if is_attribute_ident(attr, "pyo3") {
attr.parse_args_with(Punctuated::parse_terminated).map(Some)
} else {
@ -83,6 +83,19 @@ pub fn take_attributes(
Ok(())
}
pub fn take_pyo3_options<T: Parse>(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<T>> {
let mut out = Vec::new();
take_attributes(attrs, |attr| {
if let Some(options) = get_pyo3_options(attr)? {
out.extend(options.into_iter());
Ok(true)
} else {
Ok(false)
}
})?;
Ok(out)
}
pub fn get_deprecated_name_attribute(
attr: &syn::Attribute,
deprecations: &mut Deprecations,

View File

@ -1,4 +1,4 @@
use crate::attributes::{self, get_pyo3_attributes, FromPyWithAttribute};
use crate::attributes::{self, get_pyo3_options, FromPyWithAttribute};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
@ -290,7 +290,7 @@ impl ContainerOptions {
annotation: None,
};
for attr in attrs {
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
for pyo3_attr in pyo3_attrs {
match pyo3_attr {
ContainerPyO3Attribute::Transparent(kw) => {
@ -388,7 +388,7 @@ impl FieldPyO3Attributes {
let mut from_py_with = None;
for attr in attrs {
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
for pyo3_attr in pyo3_attrs {
match pyo3_attr {
FieldPyO3Attribute::Getter(field_getter) => {

View File

@ -1,7 +1,7 @@
use crate::{
attributes::{
self, get_deprecated_name_attribute, get_pyo3_attributes, is_attribute_ident,
take_attributes, NameAttribute,
self, get_deprecated_name_attribute, get_pyo3_options, is_attribute_ident, take_attributes,
NameAttribute,
},
deprecations::Deprecations,
};
@ -69,7 +69,7 @@ impl ConstAttributes {
);
attributes.is_class_attr = true;
Ok(true)
} else if let Some(pyo3_attributes) = get_pyo3_attributes(attr)? {
} else if let Some(pyo3_attributes) = get_pyo3_options(attr)? {
for pyo3_attr in pyo3_attributes {
match pyo3_attr {
PyO3ConstAttribute::Name(name) => attributes.set_name(name)?,

View File

@ -220,9 +220,8 @@ impl<'a> FnSpec<'a> {
})
}
pub fn null_terminated_python_name(&self) -> TokenStream {
let name = format!("{}\0", self.python_name);
quote!({#name})
pub fn null_terminated_python_name(&self) -> syn::LitStr {
syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span())
}
fn parse_text_signature(

View File

@ -114,7 +114,7 @@ fn get_pyfn_attr(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Option<PyFnArgs
})?;
if let Some(pyfn_args) = &mut pyfn_args {
pyfn_args.options.take_pyo3_attributes(attrs)?;
pyfn_args.options.take_pyo3_options(attrs)?;
}
Ok(pyfn_args)

View File

@ -1,6 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::method::{FnType, SelfType};
use crate::attributes::{self, take_pyo3_options, NameAttribute};
use crate::pyimpl::PyClassMethodsType;
use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, PropertyType};
use crate::utils;
@ -9,7 +9,7 @@ use quote::quote;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parse_quote, spanned::Spanned, Expr, Token};
use syn::{parse_quote, spanned::Spanned, Expr, Result, Token};
/// The parsed arguments of the pyclass macro
pub struct PyClassArgs {
@ -26,7 +26,7 @@ pub struct PyClassArgs {
}
impl Parse for PyClassArgs {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
fn parse(input: ParseStream) -> Result<Self> {
let mut slf = PyClassArgs::default();
let vars = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
@ -57,7 +57,7 @@ impl Default for PyClassArgs {
impl PyClassArgs {
/// Adda single expression from the comma separated list in the attribute, which is
/// either a single word or an assignment expression
fn add_expr(&mut self, expr: &Expr) -> syn::parse::Result<()> {
fn add_expr(&mut self, expr: &Expr) -> Result<()> {
match expr {
syn::Expr::Path(exp) if exp.path.segments.len() == 1 => self.add_path(exp),
syn::Expr::Assign(assign) => self.add_assign(assign),
@ -172,63 +172,102 @@ pub fn build_py_class(
&get_class_python_name(&class.ident, attr),
)?;
let doc = utils::get_doc(&class.attrs, text_signature, true)?;
let mut descriptors = Vec::new();
ensure_spanned!(
class.generics.params.is_empty(),
class.generics.span() => "#[pyclass] cannot have generic parameters"
);
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));
}
}
let field_options = match &mut class.fields {
syn::Fields::Named(fields) => fields
.named
.iter_mut()
.map(|field| {
FieldPyO3Options::take_pyo3_options(&mut field.attrs)
.map(move |options| (&*field, options))
})
.collect::<Result<_>>()?,
syn::Fields::Unnamed(fields) => fields
.unnamed
.iter_mut()
.map(|field| {
FieldPyO3Options::take_pyo3_options(&mut field.attrs)
.map(move |options| (&*field, options))
})
.collect::<Result<_>>()?,
syn::Fields::Unit => {
// No fields for unit struct
Vec::new()
}
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)
impl_class(&class.ident, &attr, doc, field_options, methods_type)
}
/// Parses `#[pyo3(get, set)]`
fn parse_descriptors(item: &mut syn::Field) -> syn::Result<Vec<FnType>> {
let mut descs = Vec::new();
let mut new_attrs = Vec::new();
for attr in item.attrs.drain(..) {
if let Ok(syn::Meta::List(list)) = attr.parse_meta() {
if list.path.is_ident("pyo3") {
for meta in list.nested.iter() {
if let syn::NestedMeta::Meta(metaitem) = meta {
if metaitem.path().is_ident("get") {
descs.push(FnType::Getter(SelfType::Receiver { mutable: false }));
} else if metaitem.path().is_ident("set") {
descs.push(FnType::Setter(SelfType::Receiver { mutable: true }));
} else {
bail_spanned!(metaitem.span() => "only get and set are supported");
}
}
}
} else {
new_attrs.push(attr)
}
/// `#[pyo3()]` options for pyclass fields
struct FieldPyO3Options {
get: bool,
set: bool,
name: Option<NameAttribute>,
}
enum FieldPyO3Option {
Get(attributes::kw::get),
Set(attributes::kw::set),
Name(NameAttribute),
}
impl Parse for FieldPyO3Option {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(attributes::kw::get) {
input.parse().map(FieldPyO3Option::Get)
} else if lookahead.peek(attributes::kw::set) {
input.parse().map(FieldPyO3Option::Set)
} else if lookahead.peek(attributes::kw::name) {
input.parse().map(FieldPyO3Option::Name)
} else {
new_attrs.push(attr);
Err(lookahead.error())
}
}
item.attrs = new_attrs;
Ok(descs)
}
impl FieldPyO3Options {
fn take_pyo3_options(attrs: &mut Vec<syn::Attribute>) -> Result<Self> {
let mut options = FieldPyO3Options {
get: false,
set: false,
name: None,
};
for option in take_pyo3_options(attrs)? {
match option {
FieldPyO3Option::Get(kw) => {
ensure_spanned!(
!options.get,
kw.span() => "`get` may only be specified once"
);
options.get = true;
}
FieldPyO3Option::Set(kw) => {
ensure_spanned!(
!options.set,
kw.span() => "`set` may only be specified once"
);
options.set = true;
}
FieldPyO3Option::Name(name) => {
ensure_spanned!(
options.name.is_none(),
name.0.span() => "`name` may only be specified once"
);
options.name = Some(name);
}
}
}
Ok(options)
}
}
/// To allow multiple #[pymethods] block, we define inventory types.
@ -267,12 +306,12 @@ fn impl_class(
cls: &syn::Ident,
attr: &PyClassArgs,
doc: syn::LitStr,
descriptors: Vec<(syn::Field, Vec<FnType>)>,
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
methods_type: PyClassMethodsType,
) -> syn::Result<TokenStream> {
let cls_name = get_class_python_name(cls, attr).to_string();
let extra = {
let alloc = {
if let Some(freelist) = &attr.freelist {
quote! {
impl pyo3::freelist::PyClassWithFreeList for #cls {
@ -296,17 +335,7 @@ fn impl_class(
}
};
let extra = if !descriptors.is_empty() {
let path = syn::Path::from(syn::PathSegment::from(cls.clone()));
let ty = syn::Type::from(syn::TypePath { path, qself: None });
let desc_impls = impl_descriptors(&ty, descriptors)?;
quote! {
#desc_impls
#extra
}
} else {
extra
};
let descriptors = impl_descriptors(cls, field_options)?;
// insert space for weak ref
let weakref = if attr.has_weaklist {
@ -481,39 +510,50 @@ fn impl_class(
}
}
#extra
#alloc
#descriptors
#gc_impl
})
}
fn impl_descriptors(
cls: &syn::Type,
descriptors: Vec<(syn::Field, Vec<FnType>)>,
cls: &syn::Ident,
field_options: Vec<(&syn::Field, FieldPyO3Options)>,
) -> syn::Result<TokenStream> {
let py_methods: Vec<TokenStream> = descriptors
.iter()
.flat_map(|(field, fns)| {
fns.iter()
.map(|desc| {
let doc = utils::get_doc(&field.attrs, None, true)
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
let property_type = PropertyType::Descriptor(
field.ident.as_ref().ok_or_else(
|| err_spanned!(field.span() => "`#[pyo3(get, set)]` is not supported on tuple struct fields")
)?
);
match desc {
FnType::Getter(self_ty) => {
impl_py_getter_def(cls, property_type, self_ty, &doc, &Default::default())
}
FnType::Setter(self_ty) => {
impl_py_setter_def(cls, property_type, self_ty, &doc, &Default::default())
}
_ => unreachable!(),
}
})
.collect::<Vec<syn::Result<TokenStream>>>()
let ty = syn::parse_quote!(#cls);
let py_methods: Vec<TokenStream> = field_options
.into_iter()
.enumerate()
.flat_map(|(field_index, (field, options))| {
let name_err = if options.name.is_some() && !options.get && !options.set {
Some(Err(err_spanned!(options.name.as_ref().unwrap().0.span() => "`name` is useless without `get` or `set`")))
} else {
None
};
let getter = if options.get {
Some(impl_py_getter_def(&ty, PropertyType::Descriptor {
field_index,
field,
python_name: options.name.as_ref()
}))
} else {
None
};
let setter = if options.set {
Some(impl_py_setter_def(&ty, PropertyType::Descriptor {
field_index,
field,
python_name: options.name.as_ref()
}))
} else {
None
};
name_err.into_iter().chain(getter).chain(setter)
})
.collect::<syn::Result<_>>()?;

View File

@ -2,7 +2,7 @@
use crate::{
attributes::{
self, get_deprecated_name_attribute, get_pyo3_attributes, take_attributes,
self, get_deprecated_name_attribute, get_pyo3_options, take_attributes,
FromPyWithAttribute, NameAttribute,
},
deprecations::Deprecations,
@ -62,7 +62,7 @@ impl PyFunctionArgPyO3Attributes {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut attributes = PyFunctionArgPyO3Attributes { from_py_with: None };
take_attributes(attrs, |attr| {
if let Some(pyo3_attrs) = get_pyo3_attributes(attr)? {
if let Some(pyo3_attrs) = get_pyo3_options(attr)? {
for attr in pyo3_attrs {
match attr {
PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => {
@ -270,13 +270,13 @@ impl Parse for PyFunctionOption {
impl PyFunctionOptions {
pub fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
let mut options = PyFunctionOptions::default();
options.take_pyo3_attributes(attrs)?;
options.take_pyo3_options(attrs)?;
Ok(options)
}
pub fn take_pyo3_attributes(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
pub fn take_pyo3_options(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
take_attributes(attrs, |attr| {
if let Some(pyo3_attributes) = get_pyo3_attributes(attr)? {
if let Some(pyo3_attributes) = get_pyo3_options(attr)? {
self.add_attributes(pyo3_attributes)?;
Ok(true)
} else if let Some(name) = get_deprecated_name_attribute(attr, &mut self.deprecations)?
@ -332,7 +332,7 @@ pub fn build_py_function(
ast: &mut syn::ItemFn,
mut options: PyFunctionOptions,
) -> syn::Result<TokenStream> {
options.take_pyo3_attributes(&mut ast.attrs)?;
options.take_pyo3_options(&mut ast.attrs)?;
Ok(impl_wrap_pyfunction(ast, options)?.1)
}

View File

@ -1,3 +1,6 @@
use std::borrow::Cow;
use crate::attributes::NameAttribute;
use crate::utils::ensure_not_async_fn;
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::{attributes::FromPyWithAttribute, konst::ConstSpec};
@ -10,12 +13,6 @@ use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{ext::IdentExt, spanned::Spanned, Result};
#[derive(Clone, Copy)]
pub enum PropertyType<'a> {
Descriptor(&'a syn::Ident),
Function(&'a FnSpec<'a>),
}
pub enum GeneratedPyMethod {
Method(TokenStream),
New(TokenStream),
@ -45,19 +42,19 @@ pub fn gen_py_method(
FnType::ClassAttribute => {
GeneratedPyMethod::Method(impl_py_method_class_attribute(cls, &spec))
}
FnType::Getter(self_ty) => GeneratedPyMethod::Method(impl_py_getter_def(
FnType::Getter(self_type) => GeneratedPyMethod::Method(impl_py_getter_def(
cls,
PropertyType::Function(&spec),
self_ty,
&spec.doc,
&spec.deprecations,
PropertyType::Function {
self_type,
spec: &spec,
},
)?),
FnType::Setter(self_ty) => GeneratedPyMethod::Method(impl_py_setter_def(
FnType::Setter(self_type) => GeneratedPyMethod::Method(impl_py_setter_def(
cls,
PropertyType::Function(&spec),
self_ty,
&spec.doc,
&spec.deprecations,
PropertyType::Function {
self_type,
spec: &spec,
},
)?),
})
}
@ -260,17 +257,30 @@ fn impl_call_getter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream>
/// Generate a function wrapper called `__wrap` for a property getter
pub(crate) fn impl_wrap_getter(
cls: &syn::Type,
property_type: PropertyType,
self_ty: &SelfType,
property_type: &PropertyType,
) -> syn::Result<TokenStream> {
let getter_impl = match &property_type {
PropertyType::Descriptor(ident) => {
let getter_impl = match property_type {
PropertyType::Descriptor {
field: syn::Field {
ident: Some(ident), ..
},
..
} => {
// named struct field
quote!(_slf.#ident.clone())
}
PropertyType::Function(spec) => impl_call_getter(cls, spec)?,
PropertyType::Descriptor { field_index, .. } => {
// tuple struct field
let index = syn::Index::from(*field_index);
quote!(_slf.#index.clone())
}
PropertyType::Function { spec, .. } => impl_call_getter(cls, spec)?,
};
let slf = self_ty.receiver(cls);
let slf = match property_type {
PropertyType::Descriptor { .. } => SelfType::Receiver { mutable: false }.receiver(cls),
PropertyType::Function { self_type, .. } => self_type.receiver(cls),
};
Ok(quote! {{
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject, _: *mut std::os::raw::c_void) -> *mut pyo3::ffi::PyObject
@ -309,17 +319,30 @@ fn impl_call_setter(cls: &syn::Type, spec: &FnSpec) -> syn::Result<TokenStream>
/// Generate a function wrapper called `__wrap` for a property setter
pub(crate) fn impl_wrap_setter(
cls: &syn::Type,
property_type: PropertyType,
self_ty: &SelfType,
property_type: &PropertyType,
) -> syn::Result<TokenStream> {
let setter_impl = match &property_type {
PropertyType::Descriptor(ident) => {
let setter_impl = match property_type {
PropertyType::Descriptor {
field: syn::Field {
ident: Some(ident), ..
},
..
} => {
// named struct field
quote!({ _slf.#ident = _val; })
}
PropertyType::Function(spec) => impl_call_setter(cls, spec)?,
PropertyType::Descriptor { field_index, .. } => {
// tuple struct field
let index = syn::Index::from(*field_index);
quote!({ _slf.#index = _val; })
}
PropertyType::Function { spec, .. } => impl_call_setter(cls, spec)?,
};
let slf = self_ty.receiver(cls);
let slf = match property_type {
PropertyType::Descriptor { .. } => SelfType::Receiver { mutable: true }.receiver(cls),
PropertyType::Function { self_type, .. } => self_type.receiver(cls),
};
Ok(quote! {{
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
@ -707,18 +730,11 @@ pub fn impl_py_method_def_call(
pub(crate) fn impl_py_setter_def(
cls: &syn::Type,
property_type: PropertyType,
self_ty: &SelfType,
doc: &syn::LitStr,
deprecations: &Deprecations,
) -> Result<TokenStream> {
let python_name = match property_type {
PropertyType::Descriptor(ident) => {
let formatted_name = format!("{}\0", ident.unraw());
quote!(#formatted_name)
}
PropertyType::Function(spec) => spec.null_terminated_python_name(),
};
let wrapper = impl_wrap_setter(cls, property_type, self_ty)?;
let python_name = property_type.null_terminated_python_name()?;
let deprecations = property_type.deprecations();
let doc = property_type.doc();
let wrapper = impl_wrap_setter(cls, &property_type)?;
Ok(quote! {
pyo3::class::PyMethodDefType::Setter({
#deprecations
@ -734,18 +750,11 @@ pub(crate) fn impl_py_setter_def(
pub(crate) fn impl_py_getter_def(
cls: &syn::Type,
property_type: PropertyType,
self_ty: &SelfType,
doc: &syn::LitStr,
deprecations: &Deprecations,
) -> Result<TokenStream> {
let python_name = match property_type {
PropertyType::Descriptor(ident) => {
let formatted_name = format!("{}\0", ident.unraw());
quote!(#formatted_name)
}
PropertyType::Function(spec) => spec.null_terminated_python_name(),
};
let wrapper = impl_wrap_getter(cls, property_type, self_ty)?;
let python_name = property_type.null_terminated_python_name()?;
let deprecations = property_type.deprecations();
let doc = property_type.doc();
let wrapper = impl_wrap_getter(cls, &property_type)?;
Ok(quote! {
pyo3::class::PyMethodDefType::Getter({
#deprecations
@ -770,3 +779,53 @@ fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg>, &[FnArg])
(None, args)
}
}
pub enum PropertyType<'a> {
Descriptor {
field_index: usize,
field: &'a syn::Field,
python_name: Option<&'a NameAttribute>,
},
Function {
self_type: &'a SelfType,
spec: &'a FnSpec<'a>,
},
}
impl PropertyType<'_> {
fn null_terminated_python_name(&self) -> Result<syn::LitStr> {
match self {
PropertyType::Descriptor {
field, python_name, ..
} => {
let name = match (python_name, &field.ident) {
(Some(name), _) => name.0.to_string(),
(None, Some(field_name)) => format!("{}\0", field_name.unraw()),
(None, None) => {
bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`")
}
};
Ok(syn::LitStr::new(&name, field.span()))
}
PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()),
}
}
fn deprecations(&self) -> Option<&Deprecations> {
match self {
PropertyType::Descriptor { .. } => None,
PropertyType::Function { spec, .. } => Some(&spec.deprecations),
}
}
fn doc(&self) -> Cow<syn::LitStr> {
match self {
PropertyType::Descriptor { field, .. } => {
let doc = utils::get_doc(&field.attrs, None, true)
.unwrap_or_else(|_| syn::LitStr::new("", Span::call_site()));
Cow::Owned(doc)
}
PropertyType::Function { spec, .. } => Cow::Borrowed(&spec.doc),
}
}
}

View File

@ -317,7 +317,7 @@ fn test_pymethods_from_py_with() {
}
#[pyclass]
struct TupleClass(i32);
struct TupleClass(#[pyo3(get, set, name = "value")] i32);
#[test]
fn test_tuple_struct_class() {
@ -326,5 +326,18 @@ fn test_tuple_struct_class() {
assert!(typeobj.call((), None).is_err());
py_assert!(py, typeobj, "typeobj.__name__ == 'TupleClass'");
let instance = Py::new(py, TupleClass(5)).unwrap();
py_run!(
py,
instance,
r#"
assert instance.value == 5;
instance.value = 1234;
assert instance.value == 1234;
"#
);
assert_eq!(instance.borrow(py).0, 1234);
});
}

View File

@ -159,7 +159,7 @@ mod inheriting_native_type {
#[pyclass(extends=PySet)]
#[derive(Debug)]
struct SetWithName {
#[pyo3(get(name))]
#[pyo3(get, name = "name")]
_name: &'static str,
}
@ -179,14 +179,14 @@ mod inheriting_native_type {
py_run!(
py,
set_sub,
r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""#
r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub.name == "Hello :)""#
);
}
#[pyclass(extends=PyDict)]
#[derive(Debug)]
struct DictWithName {
#[pyo3(get(name))]
#[pyo3(get, name = "name")]
_name: &'static str,
}
@ -206,7 +206,7 @@ mod inheriting_native_type {
py_run!(
py,
dict_sub,
r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub._name == "Hello :)""#
r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub.name == "Hello :)""#
);
}

View File

@ -25,6 +25,18 @@ impl ClassWithSetter {
}
#[pyclass]
struct TupleGetterSetter(#[pyo3(get, set)] i32);
struct TupleGetterSetterNoName(#[pyo3(get, set)] i32);
#[pyclass]
struct MultipleGet(#[pyo3(get, get)] i32);
#[pyclass]
struct MultipleSet(#[pyo3(set, set)] i32);
#[pyclass]
struct MultipleName(#[pyo3(name = "foo", name = "bar")] i32);
#[pyclass]
struct NameWithoutGetSet(#[pyo3(name = "value")] i32);
fn main() {}

View File

@ -16,8 +16,32 @@ 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: `#[pyo3(get, set)]` is not supported on tuple struct fields
--> $DIR/invalid_property_args.rs:28:44
error: `get` and `set` with tuple struct fields require `name`
--> $DIR/invalid_property_args.rs:28:50
|
28 | struct TupleGetterSetter(#[pyo3(get, set)] i32);
| ^^^
28 | struct TupleGetterSetterNoName(#[pyo3(get, set)] i32);
| ^^^
error: `get` may only be specified once
--> $DIR/invalid_property_args.rs:31:32
|
31 | struct MultipleGet(#[pyo3(get, get)] i32);
| ^^^
error: `set` may only be specified once
--> $DIR/invalid_property_args.rs:34:32
|
34 | struct MultipleSet(#[pyo3(set, set)] i32);
| ^^^
error: `name` may only be specified once
--> $DIR/invalid_property_args.rs:37:49
|
37 | struct MultipleName(#[pyo3(name = "foo", name = "bar")] i32);
| ^^^^^
error: `name` is useless without `get` or `set`
--> $DIR/invalid_property_args.rs:40:40
|
40 | struct NameWithoutGetSet(#[pyo3(name = "value")] i32);
| ^^^^^^^