1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use proc_macro2::{Ident, Span, TokenStream};
4use proc_macro_crate::crate_name;
5use quote::{quote, quote_spanned, ToTokens};
6use syn::{
7 meta::ParseNestedMeta, parse::Parse, punctuated::Punctuated, spanned::Spanned, token::Comma,
8 Token, Variant,
9};
10
11pub 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)]
19pub 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
27impl<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
39impl<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}
71impl<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
91pub 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 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}
111fn 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 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
136pub 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<'_>) -> …| {
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
150pub 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<&Attribute> = attrs as IntoIterator>::IntoIter
156 .into_iter()
157 .find(|attr: &&Attribute| attr.path().is_ident(attr_name));
158 if let Some(attr: &Attribute) = attr {
159 parse_nested_meta_items_from_fn(|x: &mut dyn FnMut(ParseNestedMeta<'_>) -> …| 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
167pub fn crate_ident_new() -> TokenStream {
168 use proc_macro_crate::FoundCrate;
169
170 match crate_name("glib") {
171 Ok(FoundCrate::Name(name)) => Some(name),
172 Ok(FoundCrate::Itself) => Some("glib".to_string()),
173 Err(_) => None,
174 }
175 .map(|s| {
176 let glib = Ident::new(&s, Span::call_site());
177 quote!(#glib)
178 })
179 .unwrap_or_else(|| {
180 // We couldn't find the glib crate (renamed or not) so let's just hope it's in scope!
181 //
182 // We will be able to have this information once this code is stable:
183 //
184 // ```
185 // let span = Span::call_site();
186 // let source = span.source_file();
187 // let file_path = source.path();
188 // ```
189 //
190 // Then we can use proc_macro to parse the file and check if glib is imported somehow.
191 let glib = Ident::new("glib", Span::call_site());
192 quote!(#glib)
193 })
194}
195
196// Generate i32 to enum mapping, used to implement
197// glib::translate::TryFromGlib<i32>, such as:
198//
199// if value == Animal::Goat as i32 {
200// return Some(Animal::Goat);
201// }
202pub fn gen_enum_from_glib(
203 enum_name: &Ident,
204 enum_variants: &Punctuated<Variant, Comma>,
205) -> TokenStream {
206 // FIXME: can we express this with a match()?
207 let recurse: impl Iterator = enum_variants.iter().map(|v: &Variant| {
208 let name: &Ident = &v.ident;
209 quote_spanned! { v.span() =>
210 if value == #enum_name::#name as i32 {
211 return ::core::option::Option::Some(#enum_name::#name);
212 }
213 }
214 });
215 quote! {
216 #(#recurse)*
217 ::core::option::Option::None
218 }
219}
220
221// These tests are useful to pinpoint the exact location of a macro panic
222// by running `cargo test --lib`
223#[cfg(test)]
224mod tests {
225 use syn::{parse_quote, DeriveInput};
226
227 use super::*;
228
229 fn boxed_stub() -> DeriveInput {
230 parse_quote!(
231 #[boxed_type(name = "Author")]
232 struct Author {
233 name: String,
234 }
235 )
236 }
237
238 #[test]
239 fn check_attr_found() {
240 let input = boxed_stub();
241 let found = parse_nested_meta_items(&input.attrs, "boxed_type", &mut []);
242 matches!(found, Ok(Some(_)));
243 }
244 #[test]
245 fn required_name_present() {
246 let input = boxed_stub();
247 let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name")
248 .required()
249 .value_required();
250 let _ = parse_nested_meta_items(&input.attrs, "boxed_type", &mut [&mut gtype_name]);
251 assert!(gtype_name.get_found());
252 assert_eq!(
253 gtype_name.value.map(|x| x.value()),
254 Some("Author".to_string())
255 );
256 }
257 #[test]
258 fn required_name_none() {
259 let input: DeriveInput = parse_quote!(
260 #[boxed_type(name)]
261 struct Author {
262 name: String,
263 }
264 );
265 let mut gtype_name = NestedMetaItem::<syn::LitStr>::new("name")
266 .required()
267 .value_required();
268 let found = parse_nested_meta_items(&input.attrs, "boxed_type", &mut [&mut gtype_name]);
269 // The argument value was specified as required, so an error is returned
270 assert!(found.is_err());
271 assert!(gtype_name.value.is_none());
272
273 // The argument key must be found though
274 assert!(gtype_name.get_found());
275 }
276}
277