pyclass: allow `#[pyo3(get, set, name = "foo")]`
This commit is contained in:
parent
106cf25991
commit
d011467e63
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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)?,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<_>>()?;
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 :)""#
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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);
|
||||
| ^^^^^^^
|
||||
|
|
Loading…
Reference in New Issue