From a45ee11f20716fefc03e98121714918930f4eede Mon Sep 17 00:00:00 2001 From: ranfdev Date: Mon, 8 May 2023 00:11:30 +0200 Subject: [PATCH] update to syn 2.0 and refactor attribute handling --- glib-macros/Cargo.toml | 2 +- glib-macros/src/boxed_derive.rs | 33 ++- glib-macros/src/enum_derive.rs | 49 ++-- glib-macros/src/error_domain_derive.rs | 23 +- glib-macros/src/flags_attribute.rs | 106 +++---- glib-macros/src/lib.rs | 18 +- glib-macros/src/object_subclass_attribute.rs | 2 +- glib-macros/src/properties.rs | 6 +- glib-macros/src/shared_boxed_derive.rs | 33 ++- glib-macros/src/utils.rs | 291 +++++++++++++------ glib-macros/src/value_delegate_derive.rs | 2 +- glib-macros/src/variant_derive.rs | 67 ++--- 12 files changed, 395 insertions(+), 237 deletions(-) diff --git a/glib-macros/Cargo.toml b/glib-macros/Cargo.toml index 9c0d35839ff5..bff6e9e9ac77 100644 --- a/glib-macros/Cargo.toml +++ b/glib-macros/Cargo.toml @@ -17,7 +17,7 @@ heck = "0.4" proc-macro-error = "1.0" proc-macro2 = "1.0" quote = "1.0" -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0", features = ["full"] } proc-macro-crate = "1.0" [lib] diff --git a/glib-macros/src/boxed_derive.rs b/glib-macros/src/boxed_derive.rs index e11980d680ba..0bec11252427 100644 --- a/glib-macros/src/boxed_derive.rs +++ b/glib-macros/src/boxed_derive.rs @@ -4,7 +4,7 @@ use proc_macro2::{Ident, TokenStream}; use proc_macro_error::abort_call_site; use quote::quote; -use crate::utils::{crate_ident_new, find_attribute_meta, find_nested_meta, parse_name}; +use crate::utils::{crate_ident_new, parse_nested_meta_items, NestedMetaItem}; fn gen_option_to_ptr() -> TokenStream { quote! { @@ -94,18 +94,29 @@ fn gen_impl_to_value_optional(name: &Ident, crate_ident: &TokenStream) -> TokenS pub fn impl_boxed(input: &syn::DeriveInput) -> TokenStream { let name = &input.ident; - let gtype_name = match parse_name(input, "boxed_type") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::Boxed)] requires #[boxed_type(name = \"BoxedTypeName\")]", - e - ), + let mut gtype_name = NestedMetaItem::::new("name") + .required() + .value_required(); + let mut nullable = NestedMetaItem::::new("nullable").value_optional(); + + let found = parse_nested_meta_items( + &input.attrs, + "boxed_type", + &mut [&mut gtype_name, &mut nullable], + ); + + match found { + None => { + abort_call_site!( + "#[derive(glib::Boxed)] requires #[boxed_type(name = \"BoxedTypeName\")]" + ) + } + Some((_, Err(e))) => return e.to_compile_error(), + Some((_, Ok(()))) => (), }; - let meta = find_attribute_meta(&input.attrs, "boxed_type") - .unwrap() - .unwrap(); - let nullable = find_nested_meta(&meta, "nullable").is_some(); + let gtype_name = gtype_name.value.unwrap(); + let nullable = nullable.found || nullable.value.map(|b| b.value()).unwrap_or(false); let crate_ident = crate_ident_new(); diff --git a/glib-macros/src/enum_derive.rs b/glib-macros/src/enum_derive.rs index 1f22e2ae7f8b..e0a13c5b68e3 100644 --- a/glib-macros/src/enum_derive.rs +++ b/glib-macros/src/enum_derive.rs @@ -6,9 +6,7 @@ use proc_macro_error::abort_call_site; use quote::{quote, quote_spanned}; use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Ident, Variant}; -use crate::utils::{ - crate_ident_new, gen_enum_from_glib, parse_item_attributes, parse_name, ItemAttribute, -}; +use crate::utils::{crate_ident_new, gen_enum_from_glib, parse_nested_meta_items, NestedMetaItem}; // Generate glib::gobject_ffi::GEnumValue structs mapping the enum such as: // glib::gobject_ffi::GEnumValue { @@ -29,21 +27,17 @@ fn gen_enum_values( let mut value_name = name.to_string().to_upper_camel_case(); let mut value_nick = name.to_string().to_kebab_case(); - let attrs = parse_item_attributes("enum_value", &v.attrs); - let attrs = match attrs { - Ok(attrs) => attrs, - Err(e) => abort_call_site!( - "{}: derive(glib::Enum) enum supports only the following optional attributes: #[enum_value(name = \"The Cat\", nick = \"chat\")]", - e - ), - }; - - attrs.into_iter().for_each(|attr| - match attr { - ItemAttribute::Name(n) => value_name = n, - ItemAttribute::Nick(n) => value_nick = n, - } - ); + let mut name_attr = NestedMetaItem::::new("name").value_required(); + let mut nick = NestedMetaItem::::new("nick").value_required(); + + let found = + parse_nested_meta_items(&v.attrs, "enum_value", &mut [&mut name_attr, &mut nick]); + if let Some((_, Err(e))) = found { + return e.to_compile_error(); + } + + value_name = name_attr.value.map(|s| s.value()).unwrap_or(value_name); + value_nick = nick.value.map(|s| s.value()).unwrap_or(value_nick); let value_name = format!("{value_name}\0"); let value_nick = format!("{value_nick}\0"); @@ -73,14 +67,19 @@ pub fn impl_enum(input: &syn::DeriveInput) -> TokenStream { _ => abort_call_site!("#[derive(glib::Enum)] only supports enums"), }; - let gtype_name = match parse_name(input, "enum_type") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::Enum)] requires #[enum_type(name = \"EnumTypeName\")]", - e - ), - }; + let mut gtype_name = NestedMetaItem::::new("name") + .required() + .value_required(); + let found = parse_nested_meta_items(&input.attrs, "enum_type", &mut [&mut gtype_name]); + match found { + None => { + abort_call_site!("#[derive(glib::Enum)] requires #[enum_type(name = \"EnumTypeName\")]") + } + Some((_, Err(e))) => return e.to_compile_error(), + Some((attr, _)) => attr, + }; + let gtype_name = gtype_name.value.unwrap(); let from_glib = gen_enum_from_glib(name, enum_variants); let (enum_values, nb_enum_values) = gen_enum_values(name, enum_variants); diff --git a/glib-macros/src/error_domain_derive.rs b/glib-macros/src/error_domain_derive.rs index 35e2b55d85cd..5559362116d6 100644 --- a/glib-macros/src/error_domain_derive.rs +++ b/glib-macros/src/error_domain_derive.rs @@ -5,7 +5,7 @@ use proc_macro_error::abort_call_site; use quote::quote; use syn::Data; -use crate::utils::{crate_ident_new, gen_enum_from_glib, parse_name}; +use crate::utils::{crate_ident_new, gen_enum_from_glib, parse_nested_meta_items, NestedMetaItem}; pub fn impl_error_domain(input: &syn::DeriveInput) -> TokenStream { let name = &input.ident; @@ -15,14 +15,21 @@ pub fn impl_error_domain(input: &syn::DeriveInput) -> TokenStream { _ => abort_call_site!("#[derive(glib::ErrorDomain)] only supports enums"), }; - let domain_name = match parse_name(input, "error_domain") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::ErrorDomain)] requires #[error_domain(name = \"domain-name\")]", - e - ), - }; + let mut domain_name = NestedMetaItem::::new("name") + .required() + .value_required(); + let found = parse_nested_meta_items(&input.attrs, "error_domain", &mut [&mut domain_name]); + match found { + None => { + abort_call_site!( + "#[derive(glib::ErrorDomain)] requires #[error_domain(name = \"domain-name\")]" + ) + } + Some((_, Err(e))) => return e.to_compile_error(), + _ => (), + }; + let domain_name = domain_name.value.unwrap(); let crate_ident = crate_ident_new(); let from_glib = gen_enum_from_glib(name, enum_variants); diff --git a/glib-macros/src/flags_attribute.rs b/glib-macros/src/flags_attribute.rs index 173fcd0b88dd..e68ab48acd53 100644 --- a/glib-macros/src/flags_attribute.rs +++ b/glib-macros/src/flags_attribute.rs @@ -6,21 +6,38 @@ use proc_macro_error::abort_call_site; use quote::{quote, quote_spanned}; use syn::{ punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DeriveInput, Ident, - NestedMeta, Variant, Visibility, + Variant, Visibility, }; -use crate::utils::{ - crate_ident_new, find_attribute_meta, find_nested_meta, parse_item_attributes, - parse_name_attribute, ItemAttribute, -}; +use crate::utils::{crate_ident_new, parse_nested_meta_items, NestedMetaItem}; + +pub struct AttrInput { + pub enum_name: syn::LitStr, +} +struct FlagsDesc { + variant: Variant, + name: Option, + nick: Option, + skip: bool, +} +impl FlagsDesc { + fn from_attrs(variant: Variant, attrs: &[Attribute]) -> syn::Result { + let mut name = NestedMetaItem::::new("name").value_required(); + let mut nick = NestedMetaItem::::new("nick").value_required(); + let mut skip = NestedMetaItem::::new("skip").value_optional(); -// Flag is not registered if it has the #[flags_value(skip)] meta -fn attribute_has_skip(attrs: &[Attribute]) -> bool { - let meta = find_attribute_meta(attrs, "flags_value").unwrap(); + let found = + parse_nested_meta_items(attrs, "flags_value", &mut [&mut name, &mut nick, &mut skip]); - match meta { - None => false, - Some(meta) => find_nested_meta(&meta, "skip").is_some(), + if let Some((_, Err(e))) = found { + return Err(e); + } + Ok(Self { + variant, + name: name.value.map(|s| s.value()), + nick: nick.value.map(|s| s.value()), + skip: skip.found || skip.value.map(|b| b.value()).unwrap_or(false), + }) } } @@ -38,39 +55,35 @@ fn gen_flags_values( // start at one as GFlagsValue array is null-terminated let mut n = 1; - let recurse = enum_variants.iter().filter(|v| { !attribute_has_skip(&v.attrs) } ).map(|v| { - let name = &v.ident; - let mut value_name = name.to_string().to_upper_camel_case(); - let mut value_nick = name.to_string().to_kebab_case(); - - let attrs = parse_item_attributes("flags_value", &v.attrs); - let attrs = match attrs { - Ok(attrs) => attrs, - Err(e) => abort_call_site!( - "{}: #[glib::flags] supports only the following optional attributes: #[flags_value(name = \"The Name\", nick = \"the-nick\")] or #[flags_value(skip)]", - e - ), - }; - - attrs.into_iter().for_each(|attr| - match attr { - ItemAttribute::Name(n) => value_name = n, - ItemAttribute::Nick(n) => value_nick = n, + let recurse = enum_variants + .iter() + .map(|v| FlagsDesc::from_attrs(v.clone(), &v.attrs).unwrap()) + .filter(|desc| !desc.skip) + .map(|desc| { + let v = desc.variant; + let name = &v.ident; + let mut value_name = name.to_string().to_upper_camel_case(); + let mut value_nick = name.to_string().to_kebab_case(); + + if let Some(n) = desc.name { + value_name = n; + } + if let Some(n) = desc.nick { + value_nick = n; } - ); - let value_name = format!("{value_name}\0"); - let value_nick = format!("{value_nick}\0"); + let value_name = format!("{value_name}\0"); + let value_nick = format!("{value_nick}\0"); - n += 1; - quote_spanned! {v.span()=> - #crate_ident::gobject_ffi::GFlagsValue { - value: #enum_name::#name.bits(), - value_name: #value_name as *const _ as *const _, - value_nick: #value_nick as *const _ as *const _, - }, - } - }); + n += 1; + quote_spanned! {v.span()=> + #crate_ident::gobject_ffi::GFlagsValue { + value: #enum_name::#name.bits(), + value_name: #value_name as *const _ as *const _, + value_nick: #value_nick as *const _ as *const _, + }, + } + }); ( quote! { #(#recurse)* @@ -104,15 +117,8 @@ fn gen_bitflags( } } -pub fn impl_flags(attrs: &NestedMeta, input: &DeriveInput) -> TokenStream { - let gtype_name = match parse_name_attribute(attrs) { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: [glib::flags] requires #[glib::flags(name = \"FlagsTypeName\")]", - e - ), - }; - +pub fn impl_flags(attrs: AttrInput, input: &DeriveInput) -> TokenStream { + let gtype_name = attrs.enum_name.value(); let name = &input.ident; let visibility = &input.vis; diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index 1cab8a3b13ef..6bb77a1a9520 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -16,9 +16,11 @@ mod variant_derive; mod utils; +use flags_attribute::AttrInput; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; -use syn::{parse_macro_input, DeriveInput, NestedMeta}; +use syn::{parse_macro_input, DeriveInput}; +use utils::{parse_nested_meta_items_from_stream, NestedMetaItem}; /// Macro for passing variables as strong or weak references into a closure. /// @@ -498,9 +500,19 @@ pub fn enum_derive(input: TokenStream) -> TokenStream { #[proc_macro_attribute] #[proc_macro_error] pub fn flags(attr: TokenStream, item: TokenStream) -> TokenStream { - let attr_meta = parse_macro_input!(attr as NestedMeta); + let mut name = NestedMetaItem::::new("name") + .required() + .value_required(); + + if let Err(e) = parse_nested_meta_items_from_stream(attr.into(), &mut [&mut name]) { + return e.to_compile_error().into(); + } + + let attr_meta = AttrInput { + enum_name: name.value.unwrap(), + }; let input = parse_macro_input!(item as DeriveInput); - let gen = flags_attribute::impl_flags(&attr_meta, &input); + let gen = flags_attribute::impl_flags(attr_meta, &input); gen.into() } diff --git a/glib-macros/src/object_subclass_attribute.rs b/glib-macros/src/object_subclass_attribute.rs index fc2839a2ad72..dab232f212a8 100644 --- a/glib-macros/src/object_subclass_attribute.rs +++ b/glib-macros/src/object_subclass_attribute.rs @@ -15,7 +15,7 @@ pub fn impl_object_subclass(input: &syn::ItemImpl) -> TokenStream { let mut has_class = false; for item in &input.items { match item { - syn::ImplItem::Method(method) => { + syn::ImplItem::Fn(method) => { let name = &method.sig.ident; if name == "new" || name == "with_class" { has_new = true; diff --git a/glib-macros/src/properties.rs b/glib-macros/src/properties.rs index fe6ee6f2df15..3d63464db4a7 100644 --- a/glib-macros/src/properties.rs +++ b/glib-macros/src/properties.rs @@ -42,7 +42,7 @@ impl Parse for PropsMacroInput { let wrapper_ty = derive_input .attrs .iter() - .find(|x| x.path.is_ident("properties")) + .find(|x| x.path().is_ident("properties")) .ok_or_else(|| { syn::Error::new( derive_input.span(), @@ -141,7 +141,7 @@ impl Parse for PropAttr { "builder" => { let content; parenthesized!(content in input); - let required = content.parse_terminated(syn::Expr::parse)?; + let required = content.parse_terminated(syn::Expr::parse, Token![,])?; let rest: TokenStream2 = input.parse()?; PropAttr::Builder(required, rest) } @@ -502,7 +502,7 @@ fn parse_fields(fields: syn::Fields) -> syn::Result> { } = field; attrs .into_iter() - .filter(|a| a.path.is_ident("property")) + .filter(|a| a.path().is_ident("property")) .map(move |prop_attrs| { let span = prop_attrs.span(); PropDesc::new( diff --git a/glib-macros/src/shared_boxed_derive.rs b/glib-macros/src/shared_boxed_derive.rs index 7d6fb97bda8c..7d36c8f8f3f0 100644 --- a/glib-macros/src/shared_boxed_derive.rs +++ b/glib-macros/src/shared_boxed_derive.rs @@ -4,7 +4,7 @@ use proc_macro2::{Ident, TokenStream}; use proc_macro_error::abort_call_site; use quote::quote; -use crate::utils::{crate_ident_new, find_attribute_meta, find_nested_meta, parse_name}; +use crate::utils::{crate_ident_new, parse_nested_meta_items, NestedMetaItem}; fn gen_impl_to_value_optional(name: &Ident, crate_ident: &TokenStream) -> TokenStream { let refcounted_type_prefix = refcounted_type_prefix(name, crate_ident); @@ -103,18 +103,29 @@ pub fn impl_shared_boxed(input: &syn::DeriveInput) -> proc_macro2::TokenStream { } }; - let gtype_name = match parse_name(input, "shared_boxed_type") { - Ok(name) => name, - Err(e) => abort_call_site!( - "{}: #[derive(glib::SharedBoxed)] requires #[shared_boxed_type(name = \"SharedBoxedTypeName\")]", - e - ), + let mut gtype_name = NestedMetaItem::::new("name") + .required() + .value_required(); + let mut nullable = NestedMetaItem::::new("nullable").value_optional(); + + let found = parse_nested_meta_items( + &input.attrs, + "shared_boxed_type", + &mut [&mut gtype_name, &mut nullable], + ); + + match found { + None => { + abort_call_site!( + "#[derive(glib::SharedBoxed)] requires #[shared_boxed_type(name = \"SharedBoxedTypeName\")]" + ) + } + Some((_, Err(e))) => return e.to_compile_error(), + _ => (), }; - let meta = find_attribute_meta(&input.attrs, "shared_boxed_type") - .unwrap() - .unwrap(); - let nullable = find_nested_meta(&meta, "nullable").is_some(); + let gtype_name = gtype_name.value.unwrap(); + let nullable = nullable.found || nullable.value.map(|b| b.value()).unwrap_or(false); let crate_ident = crate_ident_new(); let refcounted_type_prefix = refcounted_type_prefix(name, &crate_ident); diff --git a/glib-macros/src/utils.rs b/glib-macros/src/utils.rs index e259176206e4..c5ccdc06eb5a 100644 --- a/glib-macros/src/utils.rs +++ b/glib-macros/src/utils.rs @@ -1,112 +1,170 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use anyhow::{bail, Result}; use proc_macro2::{Ident, Span, TokenStream}; use proc_macro_crate::crate_name; -use quote::{quote, quote_spanned}; +use quote::{quote, quote_spanned, ToTokens}; use syn::{ - punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DeriveInput, Lit, Meta, - MetaList, NestedMeta, Variant, + meta::ParseNestedMeta, parse::Parse, punctuated::Punctuated, spanned::Spanned, token::Comma, + Token, Variant, }; -// find the #[@attr_name] attribute in @attrs -pub fn find_attribute_meta(attrs: &[Attribute], attr_name: &str) -> Result> { - let meta = match attrs.iter().find(|a| a.path.is_ident(attr_name)) { - Some(a) => a.parse_meta(), - _ => return Ok(None), - }; - match meta? { - Meta::List(n) => Ok(Some(n)), - _ => bail!("wrong meta type"), - } +pub trait ParseNestedMetaItem { + fn get_name(&self) -> &'static str; + fn get_found(&self) -> bool; + fn get_required(&self) -> bool; + fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option>; } -// parse a single meta like: ident = "value" -fn parse_attribute(meta: &NestedMeta) -> Result<(String, String)> { - let meta = match &meta { - NestedMeta::Meta(m) => m, - _ => bail!("wrong meta type"), - }; - let meta = match meta { - Meta::NameValue(n) => n, - _ => bail!("wrong meta type"), - }; - let value = match &meta.lit { - Lit::Str(s) => s.value(), - _ => bail!("wrong meta type"), - }; - - let ident = match meta.path.get_ident() { - None => bail!("missing ident"), - Some(ident) => ident, - }; - - Ok((ident.to_string(), value)) +#[derive(Default)] +pub struct NestedMetaItem { + pub name: &'static str, + pub value_required: bool, + pub found: bool, + pub required: bool, + pub value: Option, } -pub fn find_nested_meta<'a>(meta: &'a MetaList, name: &str) -> Option<&'a NestedMeta> { - meta.nested.iter().find(|n| match n { - NestedMeta::Meta(m) => m.path().is_ident(name), - _ => false, - }) +impl std::fmt::Debug for NestedMetaItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NestedMetaItem") + .field("name", &self.name) + .field("required", &self.required) + .field("value_required", &self.value_required) + .field("found", &self.found) + .field("value", &self.value.as_ref().map(|v| quote!(#v))) + .finish() + } } -pub fn parse_name_attribute(meta: &NestedMeta) -> Result { - let (ident, v) = parse_attribute(meta)?; - - match ident.as_ref() { - "name" => Ok(v), - s => bail!("Unknown meta {}", s), +impl NestedMetaItem { + pub const fn new(name: &'static str) -> Self { + Self { + required: false, + name, + found: false, + value_required: false, + value: None, + } + } + pub fn required(mut self) -> Self { + self.required = true; + self + } + // Note: this flags the `value` as required, that is, + // the parameter after the equal: `name = value`. + pub const fn value_required(mut self) -> Self { + self.value_required = true; + self + } + pub const fn value_optional(mut self) -> Self { + self.value_required = false; + self + } + fn parse_nested_forced(&mut self, meta: &ParseNestedMeta) -> syn::Result<()> { + if self.value_required || meta.input.peek(Token![=]) { + let _eq: Token![=] = meta.input.parse()?; + self.value = Some(meta.input.parse()?); + } + Ok(()) } } - -// Parse attribute such as: -// #[enum_type(name = "TestAnimalType")] -pub fn parse_name(input: &DeriveInput, attr_name: &str) -> Result { - let meta = match find_attribute_meta(&input.attrs, attr_name)? { - Some(meta) => meta, - _ => bail!("Missing '{}' attribute", attr_name), - }; - - let meta = match find_nested_meta(&meta, "name") { - Some(meta) => meta, - _ => bail!("Missing meta 'name'"), - }; - - parse_name_attribute(meta) +impl ParseNestedMetaItem for NestedMetaItem { + fn get_name(&self) -> &'static str { + self.name + } + fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option> { + if meta.path.is_ident(self.name) { + self.found = true; + Some(self.parse_nested_forced(meta)) + } else { + None + } + } + fn get_found(&self) -> bool { + self.found + } + fn get_required(&self) -> bool { + self.required + } } -#[derive(Debug)] -pub enum ItemAttribute { - Name(String), - Nick(String), +pub fn check_meta_items(span: Span, items: &mut [&mut dyn ParseNestedMetaItem]) -> syn::Result<()> { + let mut err: Option = None; + for item in &mut *items { + if item.get_required() && !item.get_found() { + let nerr = syn::Error::new( + span, + format!("attribute `{}` must be specified", item.get_name()), + ); + if let Some(ref mut err) = err { + err.combine(nerr); + } else { + err = Some(nerr); + } + } + } + match err { + Some(err) => Err(err), + None => Ok(()), + } +} +fn parse_nested_meta_items_from_fn( + parse_nested_meta: impl FnOnce( + &mut dyn FnMut(ParseNestedMeta) -> syn::Result<()>, + ) -> syn::Result<()>, + items: &mut [&mut dyn ParseNestedMetaItem], +) -> syn::Result<()> { + parse_nested_meta(&mut |meta| { + for item in &mut *items { + if let Some(res) = item.parse_nested(&meta) { + return res; + } + } + Err(meta.error(format!( + "unknown attribute `{}`. Possible attributes are {}", + meta.path.get_ident().unwrap(), + items + .iter() + .map(|i| format!("`{}`", i.get_name())) + .collect::>() + .join(", ") + ))) + })?; + Ok(()) } -fn parse_item_attribute(meta: &NestedMeta) -> Result { - let (ident, v) = parse_attribute(meta)?; - - match ident.as_ref() { - "name" => Ok(ItemAttribute::Name(v)), - "nick" => Ok(ItemAttribute::Nick(v)), - s => bail!("Unknown item meta {}", s), - } +pub fn parse_nested_meta_items_from_stream( + input: TokenStream, + items: &mut [&mut dyn ParseNestedMetaItem], +) -> syn::Result<()> { + parse_nested_meta_items_from_fn( + |f| { + let p = syn::meta::parser(f); + syn::parse::Parser::parse(p, input.into()) + }, + items, + )?; + check_meta_items(Span::call_site(), items) } -// Parse optional enum item attributes such as: -// #[enum_value(name = "My Name", nick = "my-nick")] -pub fn parse_item_attributes(attr_name: &str, attrs: &[Attribute]) -> Result> { - let meta = find_attribute_meta(attrs, attr_name)?; - - let v = match meta { - Some(meta) => meta - .nested - .iter() - .map(parse_item_attribute) - .collect::, _>>()?, - None => Vec::new(), - }; - - Ok(v) +#[must_use] +pub fn parse_nested_meta_items<'a>( + attrs: impl IntoIterator, + attr_name: &str, + items: &mut [&mut dyn ParseNestedMetaItem], +) -> Option<(&'a syn::Attribute, syn::Result<()>)> { + let attr = attrs + .into_iter() + .find(|attr| attr.path().is_ident(attr_name)); + if let Some(attr) = attr { + match parse_nested_meta_items_from_fn(|x| attr.parse_nested_meta(x), items) { + Ok(_) => {} + Err(err) => return Some((attr, Err(err))), + } + Some((attr, check_meta_items(attr.span(), items))) + } else { + None + } } pub fn crate_ident_new() -> TokenStream { @@ -162,3 +220,60 @@ pub fn gen_enum_from_glib( ::core::option::Option::None } } + +// These tests are useful to pinpoint the exact location of a macro panic +// by running `cargo test --lib` +#[cfg(test)] +mod tests { + use syn::{parse_quote, DeriveInput}; + + use super::*; + + fn boxed_stub() -> DeriveInput { + parse_quote!( + #[boxed_type(name = "Author")] + struct Author { + name: String, + } + ) + } + + #[test] + fn check_attr_found() { + let input = boxed_stub(); + let found = parse_nested_meta_items(&input.attrs, "boxed_type", &mut []); + matches!(found, Some((_, Ok(())))); + } + #[test] + fn required_name_present() { + let input = boxed_stub(); + let mut gtype_name = NestedMetaItem::::new("name") + .required() + .value_required(); + let _ = parse_nested_meta_items(&input.attrs, "boxed_type", &mut [&mut gtype_name]); + assert_eq!(gtype_name.get_found(), true); + assert_eq!( + gtype_name.value.map(|x| x.value()), + Some("Author".to_string()) + ); + } + #[test] + fn required_name_none() { + let input: DeriveInput = parse_quote!( + #[boxed_type(name)] + struct Author { + name: String, + } + ); + let mut gtype_name = NestedMetaItem::::new("name") + .required() + .value_required(); + let found = parse_nested_meta_items(&input.attrs, "boxed_type", &mut [&mut gtype_name]); + // The argument value was specified as required, so an error is returned + matches!(found, Some((_, Err(_)))); + assert!(gtype_name.value.is_none()); + + // The argument key must be found though + assert_eq!(gtype_name.get_found(), true); + } +} diff --git a/glib-macros/src/value_delegate_derive.rs b/glib-macros/src/value_delegate_derive.rs index 852b36804e00..7b166a5c48df 100644 --- a/glib-macros/src/value_delegate_derive.rs +++ b/glib-macros/src/value_delegate_derive.rs @@ -67,7 +67,7 @@ impl Parse for ValueDelegateInput { let args: Option = if let Some(attr) = derive_input .attrs .iter() - .find(|x| x.path.is_ident("value_delegate")) + .find(|x| x.path().is_ident("value_delegate")) { let args: Args = attr.parse_args()?; Some(args) diff --git a/glib-macros/src/variant_derive.rs b/glib-macros/src/variant_derive.rs index 779ee9be0333..018448d1575f 100644 --- a/glib-macros/src/variant_derive.rs +++ b/glib-macros/src/variant_derive.rs @@ -6,7 +6,7 @@ use proc_macro_error::abort; use quote::{format_ident, quote}; use syn::{Data, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, Type}; -use crate::utils::{crate_ident_new, find_attribute_meta}; +use crate::utils::crate_ident_new; pub fn impl_variant(input: DeriveInput) -> TokenStream { match input.data { @@ -627,31 +627,31 @@ fn derive_variant_for_c_enum( } fn get_enum_mode(attrs: &[syn::Attribute]) -> EnumMode { - let meta = find_attribute_meta(attrs, "variant_enum").unwrap(); + let attr = attrs.iter().find(|a| a.path().is_ident("variant_enum")); + + let attr = match attr { + Some(attr) => attr, + None => return EnumMode::String, + }; + let mut repr_attr = None; let mut mode = EnumMode::String; - if let Some(meta) = meta.as_ref() { - for nested in &meta.nested { - let meta = match nested { - syn::NestedMeta::Meta(m) => m, - syn::NestedMeta::Lit(s) => abort!(s, "wrong meta type"), - }; - let meta = match meta { - syn::Meta::Path(p) => p, - _ => abort!(meta, "wrong meta type"), - }; - let path = match meta.get_ident() { - Some(p) => p, - None => abort!(meta, "wrong meta type"), - }; - match path.to_string().as_ref() { - "repr" => repr_attr = Some(path), - "enum" => mode = EnumMode::Enum { repr: false }, - "flags" => mode = EnumMode::Flags { repr: false }, - s => abort!(path, "Unknown variant_enum meta {}", s), + attr.parse_nested_meta(|meta| { + match meta.path.get_ident().map(|id| id.to_string()).as_deref() { + Some("repr") => { + repr_attr = Some(meta.path); + } + Some("enum") => { + mode = EnumMode::Enum { repr: false }; } + Some("flags") => { + mode = EnumMode::Flags { repr: false }; + } + _ => abort!(meta.path, "unknown type in #[variant_enum] attribute"), } - } + Ok(()) + }) + .unwrap(); match mode { EnumMode::String if repr_attr.is_some() => { let repr_attr = repr_attr.unwrap(); @@ -674,18 +674,15 @@ fn get_enum_mode(attrs: &[syn::Attribute]) -> EnumMode { } fn get_repr(attrs: &[syn::Attribute]) -> Option { - let list = find_attribute_meta(attrs, "repr").ok()??; - for nested in list.nested { - if let syn::NestedMeta::Meta(meta @ syn::Meta::Path(_)) = nested { - if let Some(ty) = meta.path().get_ident() { - match ty.to_string().as_str() { - "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => { - return Some(ty.clone()) - } - _ => (), - } - } - } + let attr = attrs.iter().find(|a| a.path().is_ident("repr"))?; + let mut repr_ty = None; + attr.parse_nested_meta(|meta| { + repr_ty = Some(meta.path.get_ident().unwrap().clone()); + Ok(()) + }) + .unwrap(); + match repr_ty.as_ref()?.to_string().as_str() { + "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => Some(repr_ty?), + _ => None, } - None }