Skip to main content

zyn_core/extract/
mod.rs

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