| 1 | // Take a look at the license at the top of the repository in the LICENSE file. |
| 2 | |
| 3 | use proc_macro2::{Ident, Span, TokenStream}; |
| 4 | use proc_macro_crate::crate_name; |
| 5 | use quote::{quote, quote_spanned, ToTokens}; |
| 6 | use syn::{ |
| 7 | meta::ParseNestedMeta, parse::Parse, punctuated::Punctuated, spanned::Spanned, token::Comma, |
| 8 | Token, Variant, |
| 9 | }; |
| 10 | |
| 11 | pub trait ParseNestedMetaItem { |
| 12 | fn get_name(&self) -> &'static str; |
| 13 | fn get_found(&self) -> bool; |
| 14 | fn get_required(&self) -> bool; |
| 15 | fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option<syn::Result<()>>; |
| 16 | } |
| 17 | |
| 18 | #[derive (Default)] |
| 19 | pub struct NestedMetaItem<T> { |
| 20 | pub name: &'static str, |
| 21 | pub value_required: bool, |
| 22 | pub found: bool, |
| 23 | pub required: bool, |
| 24 | pub value: Option<T>, |
| 25 | } |
| 26 | |
| 27 | impl<T: Parse + ToTokens> std::fmt::Debug for NestedMetaItem<T> { |
| 28 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 29 | f&mut DebugStruct<'_, '_>.debug_struct("NestedMetaItem" ) |
| 30 | .field("name" , &self.name) |
| 31 | .field("required" , &self.required) |
| 32 | .field("value_required" , &self.value_required) |
| 33 | .field("found" , &self.found) |
| 34 | .field(name:"value" , &self.value.as_ref().map(|v: &T| quote!(#v))) |
| 35 | .finish() |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | impl<T: Parse> NestedMetaItem<T> { |
| 40 | pub const fn new(name: &'static str) -> Self { |
| 41 | Self { |
| 42 | required: false, |
| 43 | name, |
| 44 | found: false, |
| 45 | value_required: false, |
| 46 | value: None, |
| 47 | } |
| 48 | } |
| 49 | pub fn required(mut self) -> Self { |
| 50 | self.required = true; |
| 51 | self |
| 52 | } |
| 53 | // Note: this flags the `value` as required, that is, |
| 54 | // the parameter after the equal: `name = value`. |
| 55 | pub const fn value_required(mut self) -> Self { |
| 56 | self.value_required = true; |
| 57 | self |
| 58 | } |
| 59 | pub const fn value_optional(mut self) -> Self { |
| 60 | self.value_required = false; |
| 61 | self |
| 62 | } |
| 63 | fn parse_nested_forced(&mut self, meta: &ParseNestedMeta) -> syn::Result<()> { |
| 64 | if self.value_required || meta.input.peek(Token![=]) { |
| 65 | let _eq: Token![=] = meta.input.parse()?; |
| 66 | self.value = Some(meta.input.parse()?); |
| 67 | } |
| 68 | Ok(()) |
| 69 | } |
| 70 | } |
| 71 | impl<T: Parse> ParseNestedMetaItem for NestedMetaItem<T> { |
| 72 | fn get_name(&self) -> &'static str { |
| 73 | self.name |
| 74 | } |
| 75 | fn parse_nested(&mut self, meta: &ParseNestedMeta) -> Option<syn::Result<()>> { |
| 76 | if meta.path.is_ident(self.name) { |
| 77 | self.found = true; |
| 78 | Some(self.parse_nested_forced(meta)) |
| 79 | } else { |
| 80 | None |
| 81 | } |
| 82 | } |
| 83 | fn get_found(&self) -> bool { |
| 84 | self.found |
| 85 | } |
| 86 | fn get_required(&self) -> bool { |
| 87 | self.required |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | pub fn check_meta_items(span: Span, items: &mut [&mut dyn ParseNestedMetaItem]) -> syn::Result<()> { |
| 92 | let mut err: Option<syn::Error> = None; |
| 93 | for item: &mut &mut (dyn ParseNestedMetaItem + 'static) in &mut *items { |
| 94 | if item.get_required() && !item.get_found() { |
| 95 | let nerr: Error = syn::Error::new( |
| 96 | span, |
| 97 | message:format!("attribute ` {}` must be specified" , item.get_name()), |
| 98 | ); |
| 99 | if let Some(ref mut err: &mut Error) = err { |
| 100 | err.combine(another:nerr); |
| 101 | } else { |
| 102 | err = Some(nerr); |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | match err { |
| 107 | Some(err: Error) => Err(err), |
| 108 | None => Ok(()), |
| 109 | } |
| 110 | } |
| 111 | fn parse_nested_meta_items_from_fn( |
| 112 | parse_nested_meta: impl FnOnce( |
| 113 | &mut dyn FnMut(ParseNestedMeta) -> syn::Result<()>, |
| 114 | ) -> syn::Result<()>, |
| 115 | items: &mut [&mut dyn ParseNestedMetaItem], |
| 116 | ) -> syn::Result<()> { |
| 117 | parse_nested_meta(&mut |meta: ParseNestedMeta<'_>| { |
| 118 | for item: &mut &mut (dyn ParseNestedMetaItem + 'static) in &mut *items { |
| 119 | if let Some(res: Result<(), Error>) = item.parse_nested(&meta) { |
| 120 | return res; |
| 121 | } |
| 122 | } |
| 123 | Err(meta.error(msg:format!( |
| 124 | "unknown attribute ` {}`. Possible attributes are {}" , |
| 125 | meta.path.get_ident().unwrap(), |
| 126 | items |
| 127 | .iter() |
| 128 | .map(|i| format!("` {}`" , i.get_name())) |
| 129 | .collect::<Vec<_>>() |
| 130 | .join(", " ) |
| 131 | ))) |
| 132 | })?; |
| 133 | Ok(()) |
| 134 | } |
| 135 | |
| 136 | pub fn parse_nested_meta_items_from_stream( |
| 137 | input: TokenStream, |
| 138 | items: &mut [&mut dyn ParseNestedMetaItem], |
| 139 | ) -> syn::Result<()> { |
| 140 | parse_nested_meta_items_from_fn( |
| 141 | |f: &mut (dyn FnMut(ParseNestedMeta<'_>) -> … + 'static)| { |
| 142 | let p: impl Parser = syn::meta::parser(logic:f); |
| 143 | syn::parse::Parser::parse(self:p, tokens:input.into()) |
| 144 | }, |
| 145 | items, |
| 146 | )?; |
| 147 | check_meta_items(Span::call_site(), items) |
| 148 | } |
| 149 | |
| 150 | pub fn parse_nested_meta_items<'a>( |
| 151 | attrs: impl IntoIterator<Item = &'a syn::Attribute>, |
| 152 | attr_name: &str, |
| 153 | items: &mut [&mut dyn ParseNestedMetaItem], |
| 154 | ) -> syn::Result<Option<&'a syn::Attribute>> { |
| 155 | let attr: Option<&'a Attribute> = attrs as IntoIterator>::IntoIter |
| 156 | .into_iter() |
| 157 | .find(|attr: &&'a Attribute| attr.path().is_ident(attr_name)); |
| 158 | if let Some(attr: &'a Attribute) = attr { |
| 159 | parse_nested_meta_items_from_fn(|x: &mut (dyn FnMut(ParseNestedMeta<'_>) -> … + 'static)| attr.parse_nested_meta(logic:x), items)?; |
| 160 | check_meta_items(attr.span(), items)?; |
| 161 | Ok(Some(attr)) |
| 162 | } else { |
| 163 | Ok(None) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | pub fn parse_optional_nested_meta_items<'a>( |
| 168 | attrs: impl IntoIterator<Item = &'a syn::Attribute>, |
| 169 | attr_name: &str, |
| 170 | items: &mut [&mut dyn ParseNestedMetaItem], |
| 171 | ) -> syn::Result<Option<&'a syn::Attribute>> { |
| 172 | let attr: Option<&'a Attribute> = attrs as IntoIterator>::IntoIter |
| 173 | .into_iter() |
| 174 | .find(|attr: &&'a Attribute| attr.path().is_ident(attr_name)); |
| 175 | if let Some(attr: &'a Attribute) = attr { |
| 176 | if let syn::Meta::Path(_) = attr.meta { |
| 177 | Ok(Some(attr)) |
| 178 | } else { |
| 179 | parse_nested_meta_items_from_fn(|x: &mut (dyn FnMut(ParseNestedMeta<'_>) -> … + 'static)| attr.parse_nested_meta(logic:x), items)?; |
| 180 | check_meta_items(attr.span(), items)?; |
| 181 | Ok(Some(attr)) |
| 182 | } |
| 183 | } else { |
| 184 | Ok(None) |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | pub fn crate_ident_new() -> TokenStream { |
| 189 | use proc_macro_crate::FoundCrate; |
| 190 | |
| 191 | match crate_name("glib" ) { |
| 192 | Ok(FoundCrate::Name(name)) => Some(name), |
| 193 | Ok(FoundCrate::Itself) => Some("glib" .to_string()), |
| 194 | Err(_) => None, |
| 195 | } |
| 196 | .map(|s| { |
| 197 | let glib = Ident::new(&s, Span::call_site()); |
| 198 | quote!(#glib) |
| 199 | }) |
| 200 | .unwrap_or_else(|| { |
| 201 | // We couldn't find the glib crate (renamed or not) so let's just hope it's in scope! |
| 202 | // |
| 203 | // We will be able to have this information once this code is stable: |
| 204 | // |
| 205 | // ``` |
| 206 | // let span = Span::call_site(); |
| 207 | // let source = span.source_file(); |
| 208 | // let file_path = source.path(); |
| 209 | // ``` |
| 210 | // |
| 211 | // Then we can use proc_macro to parse the file and check if glib is imported somehow. |
| 212 | let glib = Ident::new("glib" , Span::call_site()); |
| 213 | quote!(#glib) |
| 214 | }) |
| 215 | } |
| 216 | |
| 217 | // Generate i32 to enum mapping, used to implement |
| 218 | // glib::translate::TryFromGlib<i32>, such as: |
| 219 | // |
| 220 | // if value == Animal::Goat as i32 { |
| 221 | // return Some(Animal::Goat); |
| 222 | // } |
| 223 | pub fn gen_enum_from_glib( |
| 224 | enum_name: &Ident, |
| 225 | enum_variants: &Punctuated<Variant, Comma>, |
| 226 | ) -> TokenStream { |
| 227 | // FIXME: can we express this with a match()? |
| 228 | let recurse: impl Iterator = enum_variants.iter().map(|v: &Variant| { |
| 229 | let name: &Ident = &v.ident; |
| 230 | quote_spanned! { v.span() => |
| 231 | if value == #enum_name::#name as i32 { |
| 232 | return ::core::option::Option::Some(#enum_name::#name); |
| 233 | } |
| 234 | } |
| 235 | }); |
| 236 | quote! { |
| 237 | #(#recurse)* |
| 238 | ::core::option::Option::None |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | // These tests are useful to pinpoint the exact location of a macro panic |
| 243 | // by running `cargo test --lib` |
| 244 | #[cfg (test)] |
| 245 | mod tests { |
| 246 | use syn::{parse_quote, DeriveInput}; |
| 247 | |
| 248 | use super::*; |
| 249 | |
| 250 | fn boxed_stub() -> DeriveInput { |
| 251 | parse_quote!( |
| 252 | #[boxed_type(name = "Author" )] |
| 253 | struct Author { |
| 254 | name: String, |
| 255 | } |
| 256 | ) |
| 257 | } |
| 258 | |
| 259 | #[test ] |
| 260 | fn check_attr_found() { |
| 261 | let input = boxed_stub(); |
| 262 | let found = parse_nested_meta_items(&input.attrs, "boxed_type" , &mut []); |
| 263 | matches!(found, Ok(Some(_))); |
| 264 | } |
| 265 | #[test ] |
| 266 | fn required_name_present() { |
| 267 | let input = boxed_stub(); |
| 268 | let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name" ) |
| 269 | .required() |
| 270 | .value_required(); |
| 271 | let _ = parse_nested_meta_items(&input.attrs, "boxed_type" , &mut [&mut gtype_name]); |
| 272 | assert!(gtype_name.get_found()); |
| 273 | assert_eq!( |
| 274 | gtype_name.value.map(|x| x.value()), |
| 275 | Some("Author" .to_string()) |
| 276 | ); |
| 277 | } |
| 278 | #[test ] |
| 279 | fn required_name_none() { |
| 280 | let input: DeriveInput = parse_quote!( |
| 281 | #[boxed_type(name)] |
| 282 | struct Author { |
| 283 | name: String, |
| 284 | } |
| 285 | ); |
| 286 | let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name" ) |
| 287 | .required() |
| 288 | .value_required(); |
| 289 | let found = parse_nested_meta_items(&input.attrs, "boxed_type" , &mut [&mut gtype_name]); |
| 290 | // The argument value was specified as required, so an error is returned |
| 291 | assert!(found.is_err()); |
| 292 | assert!(gtype_name.value.is_none()); |
| 293 | |
| 294 | // The argument key must be found though |
| 295 | assert!(gtype_name.get_found()); |
| 296 | } |
| 297 | } |
| 298 | |