Skip to main content

zyn_core/extract/
mod.rs

1use quote::ToTokens;
2use syn::Lit;
3use syn::spanned::Spanned;
4
5use crate::diagnostic::Diagnostics;
6use crate::meta::Arg;
7use crate::meta::Args;
8use crate::types::Input;
9
10mod attr;
11mod data;
12mod fields;
13mod variants;
14
15pub use attr::*;
16pub use data::*;
17pub use fields::*;
18pub use variants::*;
19
20/// Extracts a value from the macro input context.
21///
22/// Implement this trait to define how a type is resolved from an `Input`
23/// (derive or item). Built-in impls exist for `Ident`, `Generics`, and
24/// `Visibility`. The `#[element]` macro uses this trait to auto-resolve
25/// extractor parameters.
26pub trait FromInput: Sized {
27    fn from_input(input: &Input) -> crate::Result<Self>;
28}
29
30/// Generic extractor wrapper — delegates to `T::from_input`.
31///
32/// Use this in element parameters to extract any `FromInput` type
33/// without giving it a more specific semantic role like `Attr` or `Fields`.
34pub struct Extract<T: FromInput>(T);
35
36impl<T: FromInput> Extract<T> {
37    pub fn inner(self) -> T {
38        self.0
39    }
40}
41
42impl<T: FromInput> std::ops::Deref for Extract<T> {
43    type Target = T;
44
45    fn deref(&self) -> &Self::Target {
46        &self.0
47    }
48}
49
50impl<T: FromInput> std::ops::DerefMut for Extract<T> {
51    fn deref_mut(&mut self) -> &mut Self::Target {
52        &mut self.0
53    }
54}
55
56impl<T: FromInput> FromInput for Extract<T> {
57    fn from_input(input: &Input) -> crate::Result<Self> {
58        T::from_input(input).map(Extract)
59    }
60}
61
62impl FromInput for proc_macro2::Ident {
63    fn from_input(input: &Input) -> crate::Result<Self> {
64        Ok(input.ident().clone())
65    }
66}
67
68impl FromInput for syn::Generics {
69    fn from_input(input: &Input) -> crate::Result<Self> {
70        Ok(input.generics().clone())
71    }
72}
73
74impl FromInput for syn::Visibility {
75    fn from_input(input: &Input) -> crate::Result<Self> {
76        Ok(input.vis().clone())
77    }
78}
79
80/// Converts a single `Arg` into a typed value.
81///
82/// Implement this trait to support a type as a field in `#[derive(Attribute)]`
83/// structs. Built-in impls cover `bool`, `String`, integer/float primitives,
84/// `char`, `Ident`, `Path`, `Expr`, `LitStr`, `LitInt`, `Option<T>`, `Vec<T>`,
85/// and `Args`.
86pub trait FromArg: Sized {
87    fn from_arg(arg: &Arg) -> crate::Result<Self>;
88}
89
90impl FromArg for bool {
91    fn from_arg(arg: &Arg) -> crate::Result<Self> {
92        match arg {
93            Arg::Flag(_) => Ok(true),
94            _ => Err(Diagnostics::error(arg.span(), "expected flag for bool")),
95        }
96    }
97}
98
99impl FromArg for String {
100    fn from_arg(arg: &Arg) -> crate::Result<Self> {
101        match arg.as_expr_lit() {
102            Some(Lit::Str(s)) => Ok(s.value()),
103            _ => Err(Diagnostics::error(arg.span(), "expected string literal")),
104        }
105    }
106}
107
108macro_rules! impl_from_arg_int {
109    ($($t:ty),*) => {
110        $(
111            impl FromArg for $t {
112                fn from_arg(arg: &Arg) -> crate::Result<Self> {
113                    match arg.as_expr_lit() {
114                        Some(Lit::Int(i)) => i.base10_parse::<$t>().map_err(|e| Diagnostics::error(i.span(), e.to_string())),
115                        _ => Err(Diagnostics::error(arg.span(), concat!("expected integer literal for ", stringify!($t)))),
116                    }
117                }
118            }
119        )*
120    };
121}
122
123impl_from_arg_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
124
125macro_rules! impl_from_arg_float {
126    ($($t:ty),*) => {
127        $(
128            impl FromArg for $t {
129                fn from_arg(arg: &Arg) -> crate::Result<Self> {
130                    match arg.as_expr_lit() {
131                        Some(Lit::Float(f)) => f.base10_parse::<$t>().map_err(|e| Diagnostics::error(f.span(), e.to_string())),
132                        _ => Err(Diagnostics::error(arg.span(), concat!("expected float literal for ", stringify!($t)))),
133                    }
134                }
135            }
136        )*
137    };
138}
139
140impl_from_arg_float!(f32, f64);
141
142impl FromArg for char {
143    fn from_arg(arg: &Arg) -> crate::Result<Self> {
144        match arg.as_expr_lit() {
145            Some(Lit::Char(c)) => Ok(c.value()),
146            _ => Err(Diagnostics::error(arg.span(), "expected char literal")),
147        }
148    }
149}
150
151impl FromArg for syn::Ident {
152    fn from_arg(arg: &Arg) -> crate::Result<Self> {
153        match arg {
154            Arg::Flag(i) => Ok(i.clone()),
155            _ => Err(Diagnostics::error(arg.span(), "expected identifier")),
156        }
157    }
158}
159
160impl FromArg for syn::Path {
161    fn from_arg(arg: &Arg) -> crate::Result<Self> {
162        match arg {
163            Arg::Flag(i) => Ok(syn::Path::from(i.clone())),
164            _ => Err(Diagnostics::error(
165                arg.span(),
166                "expected identifier for path",
167            )),
168        }
169    }
170}
171
172impl FromArg for syn::Expr {
173    fn from_arg(arg: &Arg) -> crate::Result<Self> {
174        match arg {
175            Arg::Expr(_, expr) => Ok(expr.clone()),
176            _ => Err(Diagnostics::error(arg.span(), "expected expression")),
177        }
178    }
179}
180
181impl FromArg for syn::LitStr {
182    fn from_arg(arg: &Arg) -> crate::Result<Self> {
183        match arg.as_expr_lit() {
184            Some(Lit::Str(s)) => Ok(s.clone()),
185            _ => Err(Diagnostics::error(arg.span(), "expected string literal")),
186        }
187    }
188}
189
190impl FromArg for syn::LitInt {
191    fn from_arg(arg: &Arg) -> crate::Result<Self> {
192        match arg.as_expr_lit() {
193            Some(Lit::Int(i)) => Ok(i.clone()),
194            _ => Err(Diagnostics::error(arg.span(), "expected integer literal")),
195        }
196    }
197}
198
199impl<T: FromArg> FromArg for Option<T> {
200    fn from_arg(arg: &Arg) -> crate::Result<Self> {
201        T::from_arg(arg).map(Some)
202    }
203}
204
205impl<T: FromArg> FromArg for Vec<T> {
206    fn from_arg(arg: &Arg) -> crate::Result<Self> {
207        match arg {
208            Arg::List(_, args) => args.iter().map(T::from_arg).collect(),
209            _ => Err(Diagnostics::error(arg.span(), "expected list argument")),
210        }
211    }
212}
213
214impl FromArg for Args {
215    fn from_arg(arg: &Arg) -> crate::Result<Self> {
216        match arg {
217            Arg::List(_, args) => syn::parse2(args.to_token_stream()).map_err(Diagnostics::from),
218            _ => Err(Diagnostics::error(arg.span(), "expected list argument")),
219        }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    mod bool_impl {
228        use super::*;
229
230        #[test]
231        fn from_flag() {
232            let arg: Arg = syn::parse_str("skip").unwrap();
233            assert_eq!(bool::from_arg(&arg).unwrap(), true);
234        }
235
236        #[test]
237        fn from_expr_is_err() {
238            let arg: Arg = syn::parse_str("skip = true").unwrap();
239            assert!(bool::from_arg(&arg).is_err());
240        }
241    }
242
243    mod string_impl {
244        use super::*;
245
246        #[test]
247        fn from_expr_string_lit() {
248            let arg: Arg = syn::parse_str("rename = \"foo\"").unwrap();
249            assert_eq!(String::from_arg(&arg).unwrap(), "foo");
250        }
251
252        #[test]
253        fn from_lit_string() {
254            let arg: Arg = syn::parse_str("\"bar\"").unwrap();
255            assert_eq!(String::from_arg(&arg).unwrap(), "bar");
256        }
257
258        #[test]
259        fn from_flag_is_err() {
260            let arg: Arg = syn::parse_str("skip").unwrap();
261            assert!(String::from_arg(&arg).is_err());
262        }
263    }
264
265    mod int_impl {
266        use super::*;
267
268        #[test]
269        fn i64_from_lit() {
270            let arg: Arg = syn::parse_str("42").unwrap();
271            assert_eq!(i64::from_arg(&arg).unwrap(), 42);
272        }
273
274        #[test]
275        fn i64_from_expr() {
276            let arg: Arg = syn::parse_str("count = 7").unwrap();
277            assert_eq!(i64::from_arg(&arg).unwrap(), 7);
278        }
279
280        #[test]
281        fn i64_from_flag_is_err() {
282            let arg: Arg = syn::parse_str("skip").unwrap();
283            assert!(i64::from_arg(&arg).is_err());
284        }
285
286        #[test]
287        fn u32_from_lit() {
288            let arg: Arg = syn::parse_str("100").unwrap();
289            assert_eq!(u32::from_arg(&arg).unwrap(), 100u32);
290        }
291    }
292
293    mod char_impl {
294        use super::*;
295
296        #[test]
297        fn from_lit() {
298            let arg: Arg = syn::parse_str("'x'").unwrap();
299            assert_eq!(char::from_arg(&arg).unwrap(), 'x');
300        }
301
302        #[test]
303        fn from_flag_is_err() {
304            let arg: Arg = syn::parse_str("skip").unwrap();
305            assert!(char::from_arg(&arg).is_err());
306        }
307    }
308
309    mod ident_impl {
310        use super::*;
311
312        #[test]
313        fn from_flag() {
314            let arg: Arg = syn::parse_str("my_ident").unwrap();
315            let ident = syn::Ident::from_arg(&arg).unwrap();
316            assert_eq!(ident.to_string(), "my_ident");
317        }
318
319        #[test]
320        fn from_expr_is_err() {
321            let arg: Arg = syn::parse_str("x = 1").unwrap();
322            assert!(syn::Ident::from_arg(&arg).is_err());
323        }
324    }
325
326    mod option_impl {
327        use super::*;
328
329        #[test]
330        fn some_from_expr() {
331            let arg: Arg = syn::parse_str("rename = \"foo\"").unwrap();
332            assert_eq!(
333                Option::<String>::from_arg(&arg).unwrap(),
334                Some("foo".to_string())
335            );
336        }
337
338        #[test]
339        fn propagates_err() {
340            let arg: Arg = syn::parse_str("skip").unwrap();
341            assert!(Option::<String>::from_arg(&arg).is_err());
342        }
343    }
344
345    mod vec_impl {
346        use super::*;
347
348        #[test]
349        fn from_list() {
350            let arg: Arg = syn::parse_str("tags(\"a\", \"b\", \"c\")").unwrap();
351            let v = Vec::<String>::from_arg(&arg).unwrap();
352            assert_eq!(v, vec!["a", "b", "c"]);
353        }
354
355        #[test]
356        fn from_flag_is_err() {
357            let arg: Arg = syn::parse_str("skip").unwrap();
358            assert!(Vec::<String>::from_arg(&arg).is_err());
359        }
360    }
361
362    mod args_impl {
363        use super::*;
364
365        #[test]
366        fn from_list() {
367            let arg: Arg = syn::parse_str("inner(a = 1, b = 2)").unwrap();
368            let args = Args::from_arg(&arg).unwrap();
369            assert!(args.has("a"));
370            assert!(args.has("b"));
371        }
372
373        #[test]
374        fn from_flag_is_err() {
375            let arg: Arg = syn::parse_str("skip").unwrap();
376            assert!(Args::from_arg(&arg).is_err());
377        }
378    }
379}