Specify item key and attr name as arguments.
This commit is contained in:
parent
60fe4925f5
commit
7781bb78de
|
@ -1,7 +1,7 @@
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::{parse_quote, Attribute, DataEnum, DeriveInput, ExprCall, Fields, Ident, Result};
|
use syn::{parse_quote, Attribute, DataEnum, DeriveInput, Fields, Ident, Meta, Result};
|
||||||
|
|
||||||
/// Describes derivation input of an enum.
|
/// Describes derivation input of an enum.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -148,7 +148,7 @@ impl<'a> Container<'a> {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("Named fields should have identifiers");
|
.expect("Named fields should have identifiers");
|
||||||
let attr = FieldAttribute::parse_attrs(&field.attrs)?
|
let attr = FieldAttribute::parse_attrs(&field.attrs)?
|
||||||
.unwrap_or_else(|| FieldAttribute::Ident(parse_quote!(getattr)));
|
.unwrap_or_else(|| FieldAttribute::GetAttr(None));
|
||||||
fields.push((ident, attr))
|
fields.push((ident, attr))
|
||||||
}
|
}
|
||||||
ContainerType::Struct(fields)
|
ContainerType::Struct(fields)
|
||||||
|
@ -242,10 +242,19 @@ impl<'a> Container<'a> {
|
||||||
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
|
let mut fields: Punctuated<TokenStream, syn::Token![,]> = Punctuated::new();
|
||||||
for (ident, attr) in tups {
|
for (ident, attr) in tups {
|
||||||
let ext_fn = match attr {
|
let ext_fn = match attr {
|
||||||
FieldAttribute::IdentWithArg(expr) => quote!(#expr),
|
FieldAttribute::GetAttr(name) => {
|
||||||
FieldAttribute::Ident(meth) => {
|
if let Some(name) = name.as_ref() {
|
||||||
let arg = ident.to_string();
|
quote!(getattr(#name))
|
||||||
quote!(#meth(#arg))
|
} else {
|
||||||
|
quote!(getattr(stringify!(#ident)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FieldAttribute::GetItem(key) => {
|
||||||
|
if let Some(key) = key.as_ref() {
|
||||||
|
quote!(get_item(#key))
|
||||||
|
} else {
|
||||||
|
quote!(get_item(stringify!(#ident)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fields.push(quote!(#ident: obj.#ext_fn?.extract()?));
|
fields.push(quote!(#ident: obj.#ext_fn?.extract()?));
|
||||||
|
@ -329,9 +338,8 @@ impl ContainerAttribute {
|
||||||
/// Attributes for deriving FromPyObject scoped on fields.
|
/// Attributes for deriving FromPyObject scoped on fields.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum FieldAttribute {
|
enum FieldAttribute {
|
||||||
/// How a specific field should be extracted.
|
GetItem(Option<syn::Lit>),
|
||||||
Ident(Ident),
|
GetAttr(Option<syn::LitStr>),
|
||||||
IdentWithArg(ExprCall),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldAttribute {
|
impl FieldAttribute {
|
||||||
|
@ -340,68 +348,89 @@ impl FieldAttribute {
|
||||||
/// Currently fails if more than 1 attribute is passed in `pyo3`
|
/// Currently fails if more than 1 attribute is passed in `pyo3`
|
||||||
fn parse_attrs(attrs: &[Attribute]) -> Result<Option<Self>> {
|
fn parse_attrs(attrs: &[Attribute]) -> Result<Option<Self>> {
|
||||||
let list = get_pyo3_meta_list(attrs)?;
|
let list = get_pyo3_meta_list(attrs)?;
|
||||||
|
if list.nested.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
if list.nested.len() > 1 {
|
if list.nested.len() > 1 {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
list,
|
list,
|
||||||
"Only one of `item`, `attribute` can be provided, possibly as \
|
"Only one of `item`, `attribute` can be provided, possibly with an \
|
||||||
a key-value pair: `attribute = \"name\"`.",
|
additional argument: `item(\"key\")` or `attribute(\"name\").",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let meta = if let Some(attr) = list.nested.first() {
|
let metaitem = list.nested.into_iter().next().unwrap();
|
||||||
attr
|
let meta = match metaitem {
|
||||||
} else {
|
syn::NestedMeta::Meta(meta) => meta,
|
||||||
return Ok(None);
|
syn::NestedMeta::Lit(lit) => {
|
||||||
};
|
return Err(syn::Error::new_spanned(
|
||||||
if let syn::NestedMeta::Meta(metaitem) = meta {
|
lit,
|
||||||
let path = metaitem.path();
|
"Expected `attribute` or `item`, not a literal.",
|
||||||
let ident = Self::check_valid_ident(path)?;
|
))
|
||||||
match metaitem {
|
|
||||||
syn::Meta::NameValue(nv) => Self::get_ident_with_arg(ident, &nv.lit).map(Some),
|
|
||||||
syn::Meta::Path(_) => Ok(Some(FieldAttribute::Ident(parse_quote!(#ident)))),
|
|
||||||
_ => Err(syn::Error::new_spanned(
|
|
||||||
metaitem,
|
|
||||||
"`item` or `attribute` need to be passed alone or as key-value \
|
|
||||||
pairs, e.g. `attribute = \"name\"`.",
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
} else {
|
};
|
||||||
Err(syn::Error::new_spanned(meta, "Unexpected literal."))
|
let path = meta.path();
|
||||||
}
|
if path.is_ident("attribute") {
|
||||||
}
|
Ok(Some(FieldAttribute::GetAttr(Self::attribute_arg(meta)?)))
|
||||||
|
} else if path.is_ident("item") {
|
||||||
/// Verify the attribute path and return it if it is valid.
|
Ok(Some(FieldAttribute::GetItem(Self::item_arg(meta)?)))
|
||||||
fn check_valid_ident(path: &syn::Path) -> Result<Ident> {
|
|
||||||
if path.is_ident("item") {
|
|
||||||
Ok(parse_quote!(get_item))
|
|
||||||
} else if path.is_ident("attribute") {
|
|
||||||
Ok(parse_quote!(getattr))
|
|
||||||
} else {
|
} else {
|
||||||
Err(syn::Error::new_spanned(
|
Err(syn::Error::new_spanned(
|
||||||
path,
|
meta,
|
||||||
"Expected `item` or `attribute`",
|
"Expected `attribute` or `item`.",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to build `IdentWithArg` based on identifier and literal.
|
fn attribute_arg(meta: syn::Meta) -> syn::Result<Option<syn::LitStr>> {
|
||||||
fn get_ident_with_arg(ident: Ident, lit: &syn::Lit) -> Result<Self> {
|
let arg_list = match meta {
|
||||||
if ident == "getattr" {
|
syn::Meta::List(list) => list,
|
||||||
if let syn::Lit::Str(s) = lit {
|
syn::Meta::Path(_) => return Ok(None),
|
||||||
return Ok(FieldAttribute::IdentWithArg(parse_quote!(#ident(#s))));
|
Meta::NameValue(nv) => {
|
||||||
} else {
|
let err_msg = "Expected a string literal or no argument: `pyo3(attribute(\"name\") or `pyo3(attribute)`";
|
||||||
return Err(syn::Error::new_spanned(lit, "Expected string literal."));
|
return Err(syn::Error::new_spanned(nv, err_msg));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if arg_list.nested.len() != 1 {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
arg_list,
|
||||||
|
"Expected a single string literal.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let first = arg_list.nested.first().unwrap();
|
||||||
|
if let syn::NestedMeta::Lit(lit) = first {
|
||||||
|
if let syn::Lit::Str(litstr) = lit {
|
||||||
|
return Ok(Some(parse_quote!(#litstr)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ident == "get_item" {
|
|
||||||
return Ok(FieldAttribute::IdentWithArg(parse_quote!(#ident(#lit))));
|
|
||||||
}
|
|
||||||
|
|
||||||
// path is already checked in the `parse_attrs` loop, returning the error here anyways.
|
|
||||||
Err(syn::Error::new_spanned(
|
Err(syn::Error::new_spanned(
|
||||||
ident,
|
first,
|
||||||
"Expected `item` or `attribute`.",
|
"Expected a single string literal.",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn item_arg(meta: syn::Meta) -> syn::Result<Option<syn::Lit>> {
|
||||||
|
let arg_list = match meta {
|
||||||
|
syn::Meta::List(list) => list,
|
||||||
|
syn::Meta::Path(_) => return Ok(None),
|
||||||
|
Meta::NameValue(nv) => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv,
|
||||||
|
"Expected a literal or no argument: `pyo3(item(\"key\") or `pyo3(item)`",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if arg_list.nested.len() != 1 {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
arg_list,
|
||||||
|
"Expected a single literal.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let first = arg_list.nested.first().unwrap();
|
||||||
|
if let syn::NestedMeta::Lit(lit) = first {
|
||||||
|
return Ok(Some(parse_quote!(#lit)));
|
||||||
|
}
|
||||||
|
Err(syn::Error::new_spanned(first, "Expected a literal."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract pyo3 metalist, flattens multiple lists into a single one.
|
/// Extract pyo3 metalist, flattens multiple lists into a single one.
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub struct A<'a> {
|
||||||
s: String,
|
s: String,
|
||||||
#[pyo3(item)]
|
#[pyo3(item)]
|
||||||
t: &'a PyString,
|
t: &'a PyString,
|
||||||
#[pyo3(attribute = "foo")]
|
#[pyo3(attribute("foo"))]
|
||||||
p: &'a PyAny,
|
p: &'a PyAny,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ fn test_generic_named_fields_struct() {
|
||||||
|
|
||||||
#[derive(Debug, FromPyObject)]
|
#[derive(Debug, FromPyObject)]
|
||||||
pub struct C {
|
pub struct C {
|
||||||
#[pyo3(attribute = "test")]
|
#[pyo3(attribute("test"))]
|
||||||
test: String,
|
test: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ pub enum Foo<'a> {
|
||||||
a: Option<String>,
|
a: Option<String>,
|
||||||
},
|
},
|
||||||
StructVarGetAttrArg {
|
StructVarGetAttrArg {
|
||||||
#[pyo3(attribute = "bla")]
|
#[pyo3(attribute("bla"))]
|
||||||
a: bool,
|
a: bool,
|
||||||
},
|
},
|
||||||
StructWithGetItem {
|
StructWithGetItem {
|
||||||
|
@ -192,7 +192,7 @@ pub enum Foo<'a> {
|
||||||
a: String,
|
a: String,
|
||||||
},
|
},
|
||||||
StructWithGetItemArg {
|
StructWithGetItemArg {
|
||||||
#[pyo3(item = "foo")]
|
#[pyo3(item("foo"))]
|
||||||
a: String,
|
a: String,
|
||||||
},
|
},
|
||||||
#[pyo3(transparent)]
|
#[pyo3(transparent)]
|
||||||
|
|
Loading…
Reference in a new issue