1extern crate proc_macro;
16
17use proc_macro2::TokenStream;
18use quote::quote;
19use syn::punctuated::Punctuated;
20use syn::token::Comma;
21use syn::{
22 parse_macro_input, Expr, FnArg, GenericArgument, Ident, ItemFn, Pat, PathArguments, Type,
23 TypeSlice,
24};
25
26fn handle_required_argument(name: &Ident, ty: &Type) -> (TokenStream, Expr) {
30 (
31 TokenStream::from(quote! {
32 let #name: #ty = take_required(values.get_as(stringify!(#name))?)?;
33 }),
34 Expr::Verbatim(TokenStream::from(quote! { #name })),
35 )
36}
37
38fn handle_optional_argument(name: &Ident, inner_ty: &Type) -> (TokenStream, Expr) {
42 (
43 TokenStream::from(quote! {
44 let #name: Option<#inner_ty> = take_optional(values.get_as(stringify!(#name))?)?;
45 }),
46 Expr::Verbatim(TokenStream::from(quote! { #name })),
47 )
48}
49
50fn handle_slice_argument(name: &Ident, slice: &TypeSlice) -> (TokenStream, Expr) {
54 let elem = &slice.elem;
55 (
56 TokenStream::from(quote! {
57 let #name: Vec<#elem> = values.get_as(stringify!(#name))?;
58 }),
59 Expr::Verbatim(TokenStream::from(quote! { #name.as_slice() })),
60 )
61}
62
63fn handle_argument(arg: &FnArg) -> (TokenStream, Expr) {
69 match arg {
70 FnArg::Receiver(_) => panic!("Command callbacks cannot be member functions."),
71 FnArg::Typed(pattern_type) => {
72 match &*pattern_type.pat {
73 Pat::Ident(ident) => {
74 let ty = &pattern_type.ty;
75 let name = &ident.ident;
76
77 match &**ty {
78 Type::Reference(r) => match r.elem.as_ref() {
79 Type::Slice(s) => handle_slice_argument(name, s),
80 _ => panic!("Command callbacks only accept references of slices."),
81 },
82 Type::Path(p) => {
83 if let Some(last) = p.path.segments.last() {
84 if last.ident.to_string() == "Option" {
88 match &last.arguments {
89 PathArguments::AngleBracketed(inner_ty) => {
90 if inner_ty.args.len() != 1 {
91 panic!("Found unrecognized Option type with multiple generic type arguments");
92 }
93 match inner_ty.args.last().unwrap() {
94 GenericArgument::Type(inner_ty) => return handle_optional_argument(name, &inner_ty),
95 _ => panic!("Found unrecognized Option generic type parameter"),
96 }
97 }
98 _ => panic!("Found unrecognized, non-generic Option type"),
99 }
100 }
101 }
102
103 handle_required_argument(name, &ty)
104 }
105 _ => panic!("Invalid command callback parameter type"),
106 }
107 }
108 _ => panic!("Command callback function takes an unhandled argument type."),
109 }
110 }
111 }
112}
113
114#[proc_macro_attribute]
115pub fn command_callback(
116 args: proc_macro::TokenStream,
117 input: proc_macro::TokenStream,
118) -> proc_macro::TokenStream {
119 assert!(args.is_empty());
121
122 let input = parse_macro_input!(input as ItemFn);
123 let visibility = input.vis;
124 let constness = input.sig.constness;
125 let unsafety = input.sig.unsafety;
126 let asyncness = input.sig.asyncness;
127 let abi = input.sig.abi;
128 let name = input.sig.ident;
129
130 let args = input.sig.inputs;
131 let output = input.sig.output;
132
133 let block = input.block;
134
135 let mut arg_parsing = TokenStream::new();
136 let mut real_args: Punctuated<Expr, Comma> = Punctuated::new();
137
138 for arg in args.iter() {
139 let (parse, arg) = handle_argument(arg);
140 arg_parsing.extend(parse);
141 real_args.push(arg);
142 }
143
144 TokenStream::from(quote! {
145 #visibility #constness #unsafety #asyncness #abi fn #name(values: Values) #output {
146 #arg_parsing
147 #constness #unsafety #asyncness #abi fn inner(#args) #output #block
148 inner(#real_args)
149 }
150 })
151 .into()
152}