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