1extern crate proc_macro;
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, Span};
4use quote::{quote, ToTokens};
5use syn::{parse_macro_input, parse_quote, ItemFn};
6
7macro_rules! or_continue {
9 ( $wrapper:expr ) => {
10 match $wrapper {
11 Some(v) => v,
12 None => continue,
13 }
14 };
15}
16
17fn has_attr(attrs: &[syn::Attribute], attr_name: &str) -> bool {
18 attrs.iter().any(|a| {
19 a.parse_meta()
20 .ok()
21 .map(|meta| meta.path().is_ident(attr_name))
22 .unwrap_or(false)
23 })
24}
25
26fn has_skip_attr(attrs: &[syn::Attribute]) -> bool {
27 has_attr(attrs, "skip")
28}
29
30fn has_no_expr_attr(attrs: &[syn::Attribute]) -> bool {
31 has_attr(attrs, "no_expr")
32}
33
34fn find_ident(pat: &syn::Pat) -> Option<&Ident> {
35 match pat {
36 syn::Pat::Ident(pat_ident) => Some(&pat_ident.ident),
37 _ => None,
38 }
39}
40
41#[proc_macro_attribute]
42pub fn explain(_attr: TokenStream, item: TokenStream) -> TokenStream {
100 let mut function = parse_macro_input!(item as ItemFn);
101 let mut new_function = function.clone();
102
103 let callback = Ident::new("callback", Span::call_site());
105 let callback_arg: syn::FnArg = parse_quote! {
106 mut #callback: impl FnMut(&str, Option<&str>, &dyn std::fmt::Display)
107 };
108
109 new_function.sig.inputs.push(callback_arg);
110
111 new_function.sig.ident = Ident::new(
113 &format!("{}_explain", function.sig.ident),
114 Span::call_site(),
115 );
116
117 let new_body = &mut new_function.block;
118 new_body.stmts.clear();
119 for arg in function.sig.inputs.iter() {
120 match arg {
121 syn::FnArg::Typed(pattype) if !has_skip_attr(&pattype.attrs) => {
122 let ident = or_continue!(find_ident(&pattype.pat));
123 let ident_str = ident.to_string();
124 let ident_str = ident_str.as_str();
125 new_body.stmts.push(parse_quote! {
126 #callback(#ident_str, None, &#ident);
127 });
128 }
129 syn::FnArg::Receiver(_receiver) => (),
130 syn::FnArg::Typed(_) => (),
131 }
132 }
133 for stmt in function.block.stmts.iter_mut() {
134 match stmt {
135 syn::Stmt::Local(local) => {
136 let should_skip = has_skip_attr(&local.attrs);
137 let skip_expression = has_no_expr_attr(&local.attrs);
138 local.attrs.clear();
139 new_body.stmts.push(syn::Stmt::Local(local.clone()));
140 if should_skip {
141 continue;
142 }
143 let expr = &or_continue!(local.init.as_ref()).1;
144 let ident = or_continue!(find_ident(&local.pat));
145 let ident_str = ident.to_string();
146 let ident_str = ident_str.as_str();
147 let expr_str = expr.to_token_stream().to_string();
148 let expr_str = expr_str.as_str();
149 let expr_expr: syn::Expr = if skip_expression {
150 parse_quote! { None }
151 } else {
152 parse_quote! { Some(#expr_str) }
153 };
154 new_body.stmts.push(parse_quote! {
155 #callback(#ident_str, #expr_expr, &#ident);
156 });
157 }
158 _ => {
162 new_body.stmts.push(stmt.clone());
163 }
164 }
165 }
166
167 *new_body = parse_quote! {
168 {
169 let result = #new_body;
170 #callback("", None, &result);
171 result
172 }
173 };
174
175 (quote! {
176 #function
177 #new_function
178 })
179 .into()
180}