1mod args;
76
77use quote::{quote, ToTokens};
78use syn::{
79 parse::{Parse, Parser},
80 parse_quote,
81};
82
83#[proc_macro]
97pub fn init_depth_var(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
98 let output = if input.is_empty() {
99 quote! {
100 ::std::thread_local! {
101 static DEPTH: ::std::cell::Cell<usize> = ::std::cell::Cell::new(0);
102 }
103 }
104 } else {
105 let input2 = proc_macro2::TokenStream::from(input);
106 syn::Error::new_spanned(input2, "`init_depth_var` takes no arguments").to_compile_error()
107 };
108
109 output.into()
110}
111
112#[proc_macro_attribute]
144pub fn trace(
145 args: proc_macro::TokenStream,
146 input: proc_macro::TokenStream,
147) -> proc_macro::TokenStream {
148 let raw_args = syn::parse_macro_input!(args as syn::AttributeArgs);
149 let args = match args::Args::from_raw_args(raw_args) {
150 Ok(args) => args,
151 Err(errors) => {
152 return errors
153 .iter()
154 .map(syn::Error::to_compile_error)
155 .collect::<proc_macro2::TokenStream>()
156 .into()
157 }
158 };
159
160 let output = if let Ok(item) = syn::Item::parse.parse(input.clone()) {
161 expand_item(&args, item)
162 } else if let Ok(impl_item) = syn::ImplItem::parse.parse(input.clone()) {
163 expand_impl_item(&args, impl_item)
164 } else {
165 let input2 = proc_macro2::TokenStream::from(input);
166 syn::Error::new_spanned(input2, "expected one of: `fn`, `impl`, `mod`").to_compile_error()
167 };
168
169 output.into()
170}
171
172#[derive(Clone, Copy)]
173enum AttrApplied {
174 Directly,
175 Indirectly,
176}
177
178fn expand_item(args: &args::Args, mut item: syn::Item) -> proc_macro2::TokenStream {
179 transform_item(args, AttrApplied::Directly, &mut item);
180
181 match item {
182 syn::Item::Fn(_) | syn::Item::Mod(_) | syn::Item::Impl(_) => item.into_token_stream(),
183 _ => syn::Error::new_spanned(item, "#[trace] is not supported for this item")
184 .to_compile_error(),
185 }
186}
187
188fn expand_impl_item(args: &args::Args, mut impl_item: syn::ImplItem) -> proc_macro2::TokenStream {
189 transform_impl_item(args, AttrApplied::Directly, &mut impl_item);
190
191 match impl_item {
192 syn::ImplItem::Method(_) => impl_item.into_token_stream(),
193 _ => syn::Error::new_spanned(impl_item, "#[trace] is not supported for this impl item")
194 .to_compile_error(),
195 }
196}
197
198fn transform_item(args: &args::Args, attr_applied: AttrApplied, item: &mut syn::Item) {
199 match *item {
200 syn::Item::Fn(ref mut item_fn) => transform_fn(args, attr_applied, item_fn),
201 syn::Item::Mod(ref mut item_mod) => transform_mod(args, attr_applied, item_mod),
202 syn::Item::Impl(ref mut item_impl) => transform_impl(args, attr_applied, item_impl),
203 _ => (),
204 }
205}
206
207fn transform_fn(args: &args::Args, attr_applied: AttrApplied, item_fn: &mut syn::ItemFn) {
208 item_fn.block = Box::new(construct_traced_block(
209 &args,
210 attr_applied,
211 &item_fn.sig,
212 &item_fn.block,
213 ));
214}
215
216fn transform_mod(args: &args::Args, attr_applied: AttrApplied, item_mod: &mut syn::ItemMod) {
217 assert!(
218 (item_mod.content.is_some() && item_mod.semi.is_none())
219 || (item_mod.content.is_none() && item_mod.semi.is_some())
220 );
221
222 if item_mod.semi.is_some() {
223 unimplemented!();
224 }
225
226 if let Some((_, items)) = item_mod.content.as_mut() {
227 items.iter_mut().for_each(|item| {
228 if let AttrApplied::Directly = attr_applied {
229 match *item {
230 syn::Item::Fn(syn::ItemFn {
231 sig: syn::Signature { ref ident, .. },
232 ..
233 })
234 | syn::Item::Mod(syn::ItemMod { ref ident, .. }) => match args.filter {
235 args::Filter::Enable(ref idents) if !idents.contains(ident) => {
236 return;
237 }
238 args::Filter::Disable(ref idents) if idents.contains(ident) => {
239 return;
240 }
241 _ => (),
242 },
243 _ => (),
244 }
245 }
246
247 transform_item(args, AttrApplied::Indirectly, item);
248 });
249
250 items.insert(
251 0,
252 parse_quote! {
253 ::std::thread_local! {
254 static DEPTH: ::std::cell::Cell<usize> = ::std::cell::Cell::new(0);
255 }
256 },
257 );
258 }
259}
260
261fn transform_impl(args: &args::Args, attr_applied: AttrApplied, item_impl: &mut syn::ItemImpl) {
262 item_impl.items.iter_mut().for_each(|impl_item| {
263 if let syn::ImplItem::Method(ref mut impl_item_method) = *impl_item {
264 if let AttrApplied::Directly = attr_applied {
265 let ident = &impl_item_method.sig.ident;
266
267 match args.filter {
268 args::Filter::Enable(ref idents) if !idents.contains(ident) => {
269 return;
270 }
271 args::Filter::Disable(ref idents) if idents.contains(ident) => {
272 return;
273 }
274 _ => (),
275 }
276 }
277
278 impl_item_method.block = construct_traced_block(
279 &args,
280 AttrApplied::Indirectly,
281 &impl_item_method.sig,
282 &impl_item_method.block,
283 );
284 }
285 });
286}
287
288fn transform_impl_item(
289 args: &args::Args,
290 attr_applied: AttrApplied,
291 impl_item: &mut syn::ImplItem,
292) {
293 #[allow(clippy::single_match)]
295 match *impl_item {
296 syn::ImplItem::Method(ref mut impl_item_method) => {
297 transform_method(args, attr_applied, impl_item_method)
298 }
299 _ => (),
300 }
301}
302
303fn transform_method(
304 args: &args::Args,
305 attr_applied: AttrApplied,
306 impl_item_method: &mut syn::ImplItemMethod,
307) {
308 impl_item_method.block = construct_traced_block(
309 &args,
310 attr_applied,
311 &impl_item_method.sig,
312 &impl_item_method.block,
313 );
314}
315
316fn construct_traced_block(
317 args: &args::Args,
318 attr_applied: AttrApplied,
319 sig: &syn::Signature,
320 original_block: &syn::Block,
321) -> syn::Block {
322 let arg_idents = extract_arg_idents(args, attr_applied, &sig);
323 let arg_idents_format = arg_idents
324 .iter()
325 .map(|arg_ident| format!("{} = {{:?}}", arg_ident))
326 .collect::<Vec<_>>()
327 .join(", ");
328
329 let pretty = if args.pretty { "#" } else { "" };
330 let entering_format = format!(
331 "{{:depth$}}{} Entering {}({})",
332 args.prefix_enter, sig.ident, arg_idents_format
333 );
334 let exiting_format = format!(
335 "{{:depth$}}{} Exiting {} = {{:{}?}}",
336 args.prefix_exit, sig.ident, pretty
337 );
338
339 let pause_stmt = if args.pause {
340 quote! {{
341 use std::io::{self, BufRead};
342 let stdin = io::stdin();
343 stdin.lock().lines().next();
344 }}
345 } else {
346 quote!()
347 };
348
349 let printer = if args.logging {
350 quote! { log::trace! }
351 } else {
352 quote! { println! }
353 };
354
355 parse_quote! {{
356 #printer(#entering_format, "", #(#arg_idents,)* depth = DEPTH.with(|d| d.get()));
357 #pause_stmt
358 DEPTH.with(|d| d.set(d.get() + 2));
359 let mut fn_closure = move || #original_block;
360 let fn_return_value = fn_closure();
361 DEPTH.with(|d| d.set(d.get() - 2));
362 #printer(#exiting_format, "", fn_return_value, depth = DEPTH.with(|d| d.get()));
363 #pause_stmt
364 fn_return_value
365 }}
366}
367
368fn extract_arg_idents(
369 args: &args::Args,
370 attr_applied: AttrApplied,
371 sig: &syn::Signature,
372) -> Vec<proc_macro2::Ident> {
373 fn process_pat(
374 args: &args::Args,
375 attr_applied: AttrApplied,
376 pat: &syn::Pat,
377 arg_idents: &mut Vec<proc_macro2::Ident>,
378 ) {
379 match *pat {
380 syn::Pat::Ident(ref pat_ident) => {
381 let ident = &pat_ident.ident;
382
383 if let AttrApplied::Directly = attr_applied {
384 match args.filter {
385 args::Filter::Enable(ref idents) if !idents.contains(ident) => {
386 return;
387 }
388 args::Filter::Disable(ref idents) if idents.contains(ident) => {
389 return;
390 }
391 _ => (),
392 }
393 }
394
395 arg_idents.push(ident.clone());
396 }
397 syn::Pat::Tuple(ref pat_tuple) => {
398 pat_tuple.elems.iter().for_each(|pat| {
399 process_pat(args, attr_applied, pat, arg_idents);
400 });
401 }
402 _ => unimplemented!(),
403 }
404 }
405
406 let mut arg_idents = vec![];
407
408 for input in &sig.inputs {
409 match input {
410 syn::FnArg::Receiver(_) => (), syn::FnArg::Typed(arg_typed) => {
412 process_pat(args, attr_applied, &arg_typed.pat, &mut arg_idents);
413 }
414 }
415 }
416
417 arg_idents
418}