magic_args_derive/
lib.rs

1extern crate proc_macro;
2
3use std::collections::HashSet;
4
5use proc_macro2::TokenStream;
6use quote::{ToTokens, quote};
7use syn::parse::{Parse, ParseStream};
8use syn::{Data, DeriveInput, Index};
9
10mod keyword {
11    syn::custom_keyword!(skip);
12}
13
14enum MagicArgsAttribute {
15    Skip,
16}
17
18impl Parse for MagicArgsAttribute {
19    fn parse(input: ParseStream) -> syn::Result<Self> {
20        let lookahead = input.lookahead1();
21
22        if lookahead.peek(keyword::skip) {
23            let _skip: keyword::skip = input.parse()?;
24
25            Ok(Self::Skip)
26        } else {
27            panic!("unknown attribute")
28        }
29    }
30}
31
32#[proc_macro_derive(MagicArgs, attributes(magic_args))]
33pub fn magic_args_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
34    let item = syn::parse_macro_input!(input as DeriveInput);
35
36    let Data::Struct(data) = item.data else {
37        panic!("MagicArgs can only be derived on structs")
38    };
39
40    let mut output = TokenStream::new();
41
42    let mut types_seen = HashSet::new();
43
44    for (idx, field) in data
45        .fields
46        .into_iter()
47        .enumerate()
48        .map(|(idx, field)| (Index::from(idx), field))
49    {
50        let mut skip = false;
51
52        field
53            .attrs
54            .iter()
55            .map(|attr| attr.parse_args().unwrap())
56            .for_each(|attr: MagicArgsAttribute| match attr {
57                MagicArgsAttribute::Skip { .. } => skip = true,
58            });
59
60        if skip {
61            continue;
62        }
63
64        let field_type = field.ty;
65        let item_name = item.ident.clone();
66
67        let field_accessor = match field.ident {
68            Some(ident) => ident.to_token_stream(),
69            None => idx.to_token_stream(),
70        };
71
72        let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();
73
74        output.extend(quote! {
75            impl #impl_generics ::magic_args::Args< #field_type > for #item_name #type_generics
76                #where_clause
77            {
78                #[inline]
79                fn get(&self) -> #field_type {
80                    ::core::clone::Clone::clone(&self.#field_accessor)
81                }
82            }
83        });
84
85        if !types_seen.insert(field_type) {
86            panic!("MagicArgs cannot contain two items of the same type");
87        }
88    }
89
90    quote! {
91        const _: () = {
92            #output
93        };
94    }
95    .into()
96}