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