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::mark;
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(mark::error("expected flag for bool")
145                .span(arg.span())
146                .build()),
147        }
148    }
149}
150
151impl FromArg for String {
152    fn from_arg(arg: &Arg) -> crate::Result<Self> {
153        match arg.as_expr_lit() {
154            Some(Lit::Str(s)) => Ok(s.value()),
155            _ => Err(mark::error("expected string literal")
156                .span(arg.span())
157                .build()),
158        }
159    }
160}
161
162macro_rules! impl_from_arg_int {
163    ($($t:ty),*) => {
164        $(
165            impl FromArg for $t {
166                fn from_arg(arg: &Arg) -> crate::Result<Self> {
167                    match arg.as_expr_lit() {
168                        Some(Lit::Int(i)) => i.base10_parse::<$t>().map_err(|e| mark::error(e.to_string()).span(i.span()).build()),
169                        _ => Err(mark::error(concat!("expected integer literal for ", stringify!($t))).span(arg.span()).build()),
170                    }
171                }
172            }
173        )*
174    };
175}
176
177impl_from_arg_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
178
179macro_rules! impl_from_arg_float {
180    ($($t:ty),*) => {
181        $(
182            impl FromArg for $t {
183                fn from_arg(arg: &Arg) -> crate::Result<Self> {
184                    match arg.as_expr_lit() {
185                        Some(Lit::Float(f)) => f.base10_parse::<$t>().map_err(|e| mark::error(e.to_string()).span(f.span()).build()),
186                        _ => Err(mark::error(concat!("expected float literal for ", stringify!($t))).span(arg.span()).build()),
187                    }
188                }
189            }
190        )*
191    };
192}
193
194impl_from_arg_float!(f32, f64);
195
196impl FromArg for char {
197    fn from_arg(arg: &Arg) -> crate::Result<Self> {
198        match arg.as_expr_lit() {
199            Some(Lit::Char(c)) => Ok(c.value()),
200            _ => Err(mark::error("expected char literal")
201                .span(arg.span())
202                .build()),
203        }
204    }
205}
206
207impl FromArg for syn::Ident {
208    fn from_arg(arg: &Arg) -> crate::Result<Self> {
209        match arg {
210            Arg::Flag(i) => Ok(i.clone()),
211            _ => Err(mark::error("expected identifier").span(arg.span()).build()),
212        }
213    }
214}
215
216impl FromArg for syn::Path {
217    fn from_arg(arg: &Arg) -> crate::Result<Self> {
218        match arg {
219            Arg::Flag(i) => Ok(syn::Path::from(i.clone())),
220            _ => Err(mark::error("expected identifier for path")
221                .span(arg.span())
222                .build()),
223        }
224    }
225}
226
227impl FromArg for syn::Expr {
228    fn from_arg(arg: &Arg) -> crate::Result<Self> {
229        match arg {
230            Arg::Expr(_, expr) => Ok(expr.clone()),
231            _ => Err(mark::error("expected expression").span(arg.span()).build()),
232        }
233    }
234}
235
236impl FromArg for syn::LitStr {
237    fn from_arg(arg: &Arg) -> crate::Result<Self> {
238        match arg.as_expr_lit() {
239            Some(Lit::Str(s)) => Ok(s.clone()),
240            _ => Err(mark::error("expected string literal")
241                .span(arg.span())
242                .build()),
243        }
244    }
245}
246
247impl FromArg for syn::LitInt {
248    fn from_arg(arg: &Arg) -> crate::Result<Self> {
249        match arg.as_expr_lit() {
250            Some(Lit::Int(i)) => Ok(i.clone()),
251            _ => Err(mark::error("expected integer literal")
252                .span(arg.span())
253                .build()),
254        }
255    }
256}
257
258impl<T: FromArg> FromArg for Option<T> {
259    fn from_arg(arg: &Arg) -> crate::Result<Self> {
260        T::from_arg(arg).map(Some)
261    }
262}
263
264impl<T: FromArg> FromArg for Vec<T> {
265    fn from_arg(arg: &Arg) -> crate::Result<Self> {
266        match arg {
267            Arg::List(_, args) => args.iter().map(T::from_arg).collect(),
268            _ => Err(mark::error("expected list argument")
269                .span(arg.span())
270                .build()),
271        }
272    }
273}
274
275impl FromArg for Args {
276    fn from_arg(arg: &Arg) -> crate::Result<Self> {
277        match arg {
278            Arg::List(_, args) => Ok(args.clone()),
279            _ => Err(mark::error("expected list argument")
280                .span(arg.span())
281                .build()),
282        }
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    mod bool_impl {
291        use super::*;
292
293        #[test]
294        fn from_flag() {
295            let arg: Arg = syn::parse_str("skip").unwrap();
296            assert!(bool::from_arg(&arg).unwrap());
297        }
298
299        #[test]
300        fn from_expr_is_err() {
301            let arg: Arg = syn::parse_str("skip = true").unwrap();
302            assert!(bool::from_arg(&arg).is_err());
303        }
304    }
305
306    mod string_impl {
307        use super::*;
308
309        #[test]
310        fn from_expr_string_lit() {
311            let arg: Arg = syn::parse_str("rename = \"foo\"").unwrap();
312            assert_eq!(String::from_arg(&arg).unwrap(), "foo");
313        }
314
315        #[test]
316        fn from_lit_string() {
317            let arg: Arg = syn::parse_str("\"bar\"").unwrap();
318            assert_eq!(String::from_arg(&arg).unwrap(), "bar");
319        }
320
321        #[test]
322        fn from_flag_is_err() {
323            let arg: Arg = syn::parse_str("skip").unwrap();
324            assert!(String::from_arg(&arg).is_err());
325        }
326    }
327
328    mod int_impl {
329        use super::*;
330
331        #[test]
332        fn i64_from_lit() {
333            let arg: Arg = syn::parse_str("42").unwrap();
334            assert_eq!(i64::from_arg(&arg).unwrap(), 42);
335        }
336
337        #[test]
338        fn i64_from_expr() {
339            let arg: Arg = syn::parse_str("count = 7").unwrap();
340            assert_eq!(i64::from_arg(&arg).unwrap(), 7);
341        }
342
343        #[test]
344        fn i64_from_flag_is_err() {
345            let arg: Arg = syn::parse_str("skip").unwrap();
346            assert!(i64::from_arg(&arg).is_err());
347        }
348
349        #[test]
350        fn u32_from_lit() {
351            let arg: Arg = syn::parse_str("100").unwrap();
352            assert_eq!(u32::from_arg(&arg).unwrap(), 100u32);
353        }
354    }
355
356    mod char_impl {
357        use super::*;
358
359        #[test]
360        fn from_lit() {
361            let arg: Arg = syn::parse_str("'x'").unwrap();
362            assert_eq!(char::from_arg(&arg).unwrap(), 'x');
363        }
364
365        #[test]
366        fn from_flag_is_err() {
367            let arg: Arg = syn::parse_str("skip").unwrap();
368            assert!(char::from_arg(&arg).is_err());
369        }
370    }
371
372    mod ident_impl {
373        use super::*;
374
375        #[test]
376        fn from_flag() {
377            let arg: Arg = syn::parse_str("my_ident").unwrap();
378            let ident = syn::Ident::from_arg(&arg).unwrap();
379            assert_eq!(ident.to_string(), "my_ident");
380        }
381
382        #[test]
383        fn from_expr_is_err() {
384            let arg: Arg = syn::parse_str("x = 1").unwrap();
385            assert!(syn::Ident::from_arg(&arg).is_err());
386        }
387    }
388
389    mod option_impl {
390        use super::*;
391
392        #[test]
393        fn some_from_expr() {
394            let arg: Arg = syn::parse_str("rename = \"foo\"").unwrap();
395            assert_eq!(
396                Option::<String>::from_arg(&arg).unwrap(),
397                Some("foo".to_string())
398            );
399        }
400
401        #[test]
402        fn propagates_err() {
403            let arg: Arg = syn::parse_str("skip").unwrap();
404            assert!(Option::<String>::from_arg(&arg).is_err());
405        }
406    }
407
408    mod vec_impl {
409        use super::*;
410
411        #[test]
412        fn from_list() {
413            let arg: Arg = syn::parse_str("tags(\"a\", \"b\", \"c\")").unwrap();
414            let v = Vec::<String>::from_arg(&arg).unwrap();
415            assert_eq!(v, vec!["a", "b", "c"]);
416        }
417
418        #[test]
419        fn from_flag_is_err() {
420            let arg: Arg = syn::parse_str("skip").unwrap();
421            assert!(Vec::<String>::from_arg(&arg).is_err());
422        }
423    }
424
425    mod args_impl {
426        use super::*;
427
428        #[test]
429        fn from_list() {
430            let arg: Arg = syn::parse_str("inner(a = 1, b = 2)").unwrap();
431            let args = Args::from_arg(&arg).unwrap();
432            assert!(args.has("a"));
433            assert!(args.has("b"));
434        }
435
436        #[test]
437        fn from_flag_is_err() {
438            let arg: Arg = syn::parse_str("skip").unwrap();
439            assert!(Args::from_arg(&arg).is_err());
440        }
441    }
442}