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}