report_macros/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use syn::{parse_macro_input, parse_quote, parse_quote_spanned, spanned::Spanned, Attribute, Block, Error, Expr, ItemFn, ExprMacro, Result, Stmt};
6use quote::ToTokens;
7///Print all nested logging events to the console.
8///
9///## Usage
10///
11///The [`log`](macro@log) attribute macro is used to print all logging-related
12///events in the function it is applied to. This includes all calls
13///made in nested functions. If [`log`](macro@log) is applied to a nested function,
14///two separate reports will be generated.
15///
16///```
17///use report::{log, info};
18///
19///#[log("First report")]
20///fn main() {
21///    info!("This info is attached to the first report");
22///    function();
23///    nested()
24///}
25///
26///#[log("Second report")]
27///fn function() {
28///    info!("This info is attached to the second report");
29///}
30///
31///
32///fn nested() {
33///    info!("This info is also attached to the first report");
34///}
35///```
36///
37///```text
38///╭────────────────────────────────────────────────────────────────────────────────────────────╮
39///│ Second report                                                                              │
40///├─┬──────────────────────────────────────────────────────────────────────────────────────────┤
41///│ ╰── info: This info is attached to the second report                                       │
42///╰────────────────────────────────────────────────────────────────────────────────────────────╯
43///╭────────────────────────────────────────────────────────────────────────────────────────────╮
44///│ First report                                                                               │
45///├─┬──────────────────────────────────────────────────────────────────────────────────────────┤
46///│ ├── info: This info is attached to the first report                                        │
47///│ ╰── info: This info is also attached to the first report                                   │
48///╰────────────────────────────────────────────────────────────────────────────────────────────╯
49///```
50///
51///## Formatting arguments
52///
53///It's possible to use the arguments of the function in the format
54///string.
55///
56///```
57///use report::log;
58///
59///#[log("The argument is {}", arg)]
60///fn function(arg: i32) -> i32 {
61///    return arg;
62///}
63///```
64///
65///This macro should only be used in application code and not in
66///libraries, so that a user can integrate generated reports into
67///their own, making the grouping of related information easier.
68#[proc_macro_attribute]
69pub fn log(args: TokenStream, input: TokenStream) -> TokenStream {
70    let mut item = parse_macro_input!(input as ItemFn);
71    let args = TokenStream2::from(args);
72
73    item.block.stmts.insert(0, parse_quote!(
74        let _logger = ::report::Report::log(|| format!(#args));
75    ));
76
77    return TokenStream::from(item.to_token_stream())
78}
79
80///Annotate a new logging group with a custom message.
81///
82///## Usage
83///
84///Currently, the ability to use proc macro attributes on expressions
85///is only available when enabling the `proc-macro-hygiene` feature
86///using the nightly compiler. To circumvent this limitation, [`report`](macro@report)
87///will parse the functions and expand all subsequent calls to the
88///similarly named attribute macro. In the following steps, any expression
89///annotated with a report will display all nested logging events under
90///the same group. If there are no events to be logged, the group header
91///will not be included in the final report.
92///
93///```
94///use report::{report, info, log};
95///
96///#[report]
97///#[log("Test report")]
98///fn main() {
99///    
100///    #[report("First group")]
101///    {
102///        info!("This info is attached to the first group");
103///    }
104///
105///    #[report("Second group")]
106///    info!("This info is attached to the second group");
107///
108///    #[report("Omitted group")]
109///    {
110///        //This group will not be included, since there are no events
111///    }
112///}
113///```
114///
115///```text
116///╭────────────────────────────────────────────────────────────────────────────────────────────╮
117///│ Test report                                                                                │
118///├─┬──────────────────────────────────────────────────────────────────────────────────────────┤
119///│ ├── First group                                                                            │
120///│ │   ╰── info: This info is attached to the first group                                     │
121///│ ╰── Second group                                                                           │
122///│     ╰── info: This info is attached to the second group                                    │
123///╰────────────────────────────────────────────────────────────────────────────────────────────╯
124///```
125///
126///## Borrowing of format arguments
127///
128///Just like any other macro in this crate, the format string used by
129///the println! macro can be supplied as an argument. It is, however,
130///important to note that the actual string formatting happens **after**
131///the expression is evaluated. This means all arguments will be borrowed
132///for the scope of the expression and will make it impossible to use
133///elements that are moved and do not implement the `Copy`
134///trait. Mutable references can’t be used in the formatting string, either.
135///The reason for this is that a group may be excluded from the report,
136///making it a waste of resources to format the string in advance.
137///
138///```compile_fail
139///use report::{Result, report};
140///use std::fs::File;
141///
142///#[report]
143///fn open_file() -> Result {
144///    let path = String::from("Cargo.toml");
145///    #[report("Opening file {path:?}")]
146///    let _file = File::open(path)?; //cannot move out of 'path' because it is borrowed
147///    Ok(())
148///}
149///```
150///
151///```
152///use report::{Result, report};
153///use std::fs::File;
154///
155///#[report]
156///fn open_file() -> Result {
157///    let path = String::from("Cargo.toml");
158///    #[report("Opening file {path:?}")]
159///    let _file = File::open(path.as_str())?; //this works because path is no longer moved
160///    Ok(())
161///}
162///```
163
164#[proc_macro_attribute]
165pub fn report(args: TokenStream, input: TokenStream) -> TokenStream {
166
167    let mut item = parse_macro_input!(input as ItemFn);
168
169    if !args.is_empty() {
170        let args = TokenStream2::from(args);
171        let mut error = Error::new_spanned(args, "This attribute does not take any arguments")
172            .to_compile_error();
173        error.extend(item.to_token_stream());
174        return TokenStream::from(error);    
175    }
176
177    if let Err(err) = iter_block(&mut item.block) {
178        return TokenStream::from(err.to_compile_error())
179    }
180
181    return TokenStream::from(item.to_token_stream())
182}
183
184fn process_expr(expr: &mut Expr, local_attrs: Option<&mut Vec<Attribute>>) -> Result<()> {
185    iter_expr(expr)?;
186
187    let mut attrs = Vec::new();
188
189    if let Some(local_attrs) = local_attrs {
190        local_attrs.retain(|attr| {
191            let res = !attr.path().is_ident("report");
192            if !res { attrs.push(attr.clone()) }
193            res
194        });
195    }
196
197    if let Some(expr_attrs) = get_attrs(expr) {
198        expr_attrs.retain(|attr| {
199            let res = !attr.path().is_ident("report");
200            if !res { attrs.push(attr.clone()) }
201            res
202        });
203    }
204
205    for attr in attrs {
206        let list = attr.meta.require_list()?.tokens.clone(); 
207        *expr = parse_quote_spanned!(attr.span() => {
208            let _logger = ::report::Report::rec(|| format!(#list));
209            #expr
210        });
211    }
212
213    return Ok(());
214}
215
216fn iter_block(block: &mut Block) -> Result<()> {
217    for statement in block.stmts.iter_mut() {
218        match statement {
219            Stmt::Local(local) => if let Some(init) = local.init.as_mut() {
220                process_expr(&mut init.expr, Some(&mut local.attrs))?;
221                if let Some((.., expr)) = init.diverge.as_mut() {
222                    iter_expr(expr)?;
223                }
224            },
225            Stmt::Expr(expr, ..) => {
226                process_expr(expr, None)?;
227            },
228            Stmt::Macro(macro_expr) => {
229                let mut attrs = Vec::new();
230
231                macro_expr.attrs.retain(|attr| {
232                    let res = !attr.path().is_ident("report");
233                    if !res { attrs.push(attr.clone()) }
234                    res
235                });
236
237                if attrs.is_empty() { continue } 
238                let mut expr = Expr::Macro(ExprMacro {
239                    attrs,
240                    mac: macro_expr.mac.clone()
241                });
242
243                process_expr(&mut expr, None)?;
244                *statement = Stmt::Expr(expr, macro_expr.semi_token)
245            },
246            Stmt::Item(..) => ()
247        }
248    }
249    Ok(())
250}
251
252fn iter_expr(expr: &mut Expr) -> Result<()> {
253    match expr {
254        Expr::Try(try_expr) => process_expr(&mut try_expr.expr, None),
255        Expr::Call(call_expr) => {
256            process_expr(&mut call_expr.func, None)?;
257            for arg in call_expr.args.iter_mut() {
258                process_expr(arg, None)?;
259            }
260            Ok(())
261        },
262        Expr::Tuple(tuple_expr) => {
263            for expr in tuple_expr.elems.iter_mut() {
264                process_expr(expr, None)?;
265            }
266            Ok(())
267        },
268        Expr::Macro(..) => Ok(()),
269        Expr::MethodCall(method_call_expr) => {
270            process_expr(&mut method_call_expr.receiver, None)?;
271            for arg in method_call_expr.args.iter_mut() {
272                process_expr(arg, None)?;
273            }
274            Ok(())
275        },
276        Expr::Match(match_expr) => {
277            process_expr(&mut match_expr.expr, None)?;
278            for arm in match_expr.arms.iter_mut() {
279                process_expr(arm.body.as_mut(), Some(arm.attrs.as_mut()))?;
280            }
281            Ok(())
282        },
283        Expr::Closure(closure_expr) => {
284            process_expr(&mut closure_expr.body, None)?;
285            Ok(())
286        },
287        Expr::Unsafe(unsafe_expr) => iter_block(&mut unsafe_expr.block),
288        Expr::Block(block_expr) => iter_block(&mut block_expr.block),
289        Expr::Assign(assign_expr) => {
290            process_expr(&mut assign_expr.left, None)?;
291            process_expr(&mut assign_expr.right, None)
292        },
293        Expr::Field(field_expr) => process_expr(&mut field_expr.base, None),
294        Expr::Index(index_expr) => {
295            process_expr(&mut index_expr.expr, None)?;
296            process_expr(&mut index_expr.index, None)
297        },
298        Expr::Range(range_expr) => {
299            if let Some(start) = range_expr.start.as_mut() {
300                process_expr(start, None)?;
301            }
302            if let Some(end) = range_expr.end.as_mut() {
303                process_expr(end, None)?;
304            }
305            Ok(())
306        },
307        Expr::Path(..) => Ok(()),
308        Expr::Reference(reference_expr) => process_expr(&mut reference_expr.expr, None),
309        Expr::Break(break_expr) => if let Some(expr) = break_expr.expr.as_mut() {
310            process_expr(expr, None)
311        } else { Ok(()) },
312        Expr::Continue(..) => Ok(()),
313        Expr::Return(return_expr) => if let Some(expr) = return_expr.expr.as_mut() {
314            process_expr(expr, None)
315        } else { Ok(()) },
316        Expr::Struct(struct_expr) => {
317            for field in struct_expr.fields.iter_mut() {
318                process_expr(&mut field.expr, None)?;
319            }
320            Ok(())
321        },
322        Expr::Repeat(repeat_expr) => {
323            process_expr(&mut repeat_expr.expr, None)?;
324            process_expr(&mut repeat_expr.len, None)
325        },
326        Expr::Paren(paren_expr) => process_expr(&mut paren_expr.expr, None),
327        Expr::Group(group_expr) => process_expr(&mut group_expr.expr, None),
328        Expr::TryBlock(try_block_expr) => iter_block(&mut try_block_expr.block),
329        Expr::Async(async_expr) => iter_block(&mut async_expr.block),
330        Expr::Await(await_expr) => process_expr(&mut await_expr.base, None),
331        Expr::Yield(yield_expr) => if let Some(expr) = yield_expr.expr.as_mut() {
332            process_expr(expr, None)
333        } else { Ok(()) },
334        Expr::ForLoop(for_loop_expr) => {
335            process_expr(&mut for_loop_expr.expr, None)?;
336            iter_block(&mut for_loop_expr.body)
337        },
338        Expr::While(while_expr) => {
339            process_expr(&mut while_expr.cond, None)?;
340            iter_block(&mut while_expr.body)
341        },
342        Expr::Loop(loop_expr) => iter_block(&mut loop_expr.body),
343        Expr::If(if_expr) => {
344            process_expr(&mut if_expr.cond, None)?;
345            iter_block(&mut if_expr.then_branch)?;
346            if let Some((_, else_branch)) = if_expr.else_branch.as_mut() {
347                process_expr(else_branch, None)?;
348            }
349            Ok(())
350        },
351        Expr::Let(let_expr) => process_expr(&mut let_expr.expr, None),
352        Expr::Lit(..) => Ok(()),
353        Expr::Cast(cast_expr) => {
354            process_expr(&mut cast_expr.expr, None)?;
355            Ok(())
356        },
357        Expr::Infer(..) => Ok(()),
358        Expr::Array(array_expr) => {
359            for expr in array_expr.elems.iter_mut() {
360                process_expr(expr, None)?;
361            }
362            Ok(())
363        },
364        Expr::Unary(unary_expr) => process_expr(&mut unary_expr.expr, None),
365        Expr::Binary(binary_expr) => {
366            process_expr(&mut binary_expr.left, None)?;
367            process_expr(&mut binary_expr.right, None)
368        },
369        Expr::Const(const_expr) => iter_block(&mut const_expr.block),
370        _ => Ok(())
371    }
372}
373
374fn get_attrs(expr: &mut Expr) -> Option<&mut Vec<Attribute>> {
375    match expr {
376        Expr::Try(try_expr) => Some(&mut try_expr.attrs),
377        Expr::Call(call_expr) => Some(&mut call_expr.attrs),
378        Expr::Tuple(tuple_expr) => Some(&mut tuple_expr.attrs),
379        Expr::Macro(macro_expr) => Some(&mut macro_expr.attrs),
380        Expr::MethodCall(method_call_expr) => Some(&mut method_call_expr.attrs),
381        Expr::Match(match_expr) => Some(&mut match_expr.attrs),
382        Expr::Closure(closure_expr) => Some(&mut closure_expr.attrs),
383        Expr::Unsafe(unsafe_expr) => Some(&mut unsafe_expr.attrs),
384        Expr::Block(block_expr) => Some(&mut block_expr.attrs),
385        Expr::Assign(assign_expr) => Some(&mut assign_expr.attrs),
386        Expr::Field(field_expr) => Some(&mut field_expr.attrs),
387        Expr::Index(index_expr) => Some(&mut index_expr.attrs),
388        Expr::Range(range_expr) => Some(&mut range_expr.attrs),
389        Expr::Path(path_expr) => Some(&mut path_expr.attrs),
390        Expr::Reference(reference_expr) => Some(&mut reference_expr.attrs),
391        Expr::Break(break_expr) => Some(&mut break_expr.attrs),
392        Expr::Continue(continue_expr) => Some(&mut continue_expr.attrs),
393        Expr::Return(return_expr) => Some(&mut return_expr.attrs),
394        Expr::Struct(struct_expr) => Some(&mut struct_expr.attrs),
395        Expr::Repeat(repeat_expr) => Some(&mut repeat_expr.attrs),
396        Expr::Paren(paren_expr) => Some(&mut paren_expr.attrs),
397        Expr::Group(group_expr) => Some(&mut group_expr.attrs),
398        Expr::TryBlock(try_block_expr) => Some(&mut try_block_expr.attrs),
399        Expr::Async(async_expr) => Some(&mut async_expr.attrs),
400        Expr::Await(await_expr) => Some(&mut await_expr.attrs),
401        Expr::Yield(yield_expr) => Some(&mut yield_expr.attrs),
402        Expr::ForLoop(for_loop_expr) => Some(&mut for_loop_expr.attrs),
403        Expr::While(while_expr) => Some(&mut while_expr.attrs),
404        Expr::Loop(loop_expr) => Some(&mut loop_expr.attrs),
405        Expr::If(if_expr) => Some(&mut if_expr.attrs),
406        Expr::Let(let_expr) => Some(&mut let_expr.attrs),
407        Expr::Lit(lit_expr) => Some(&mut lit_expr.attrs),
408        Expr::Cast(cast_expr) => Some(&mut cast_expr.attrs),
409        Expr::Infer(info_expr) => Some(&mut info_expr.attrs),
410        Expr::Array(array_expr) => Some(&mut array_expr.attrs),
411        Expr::Unary(unary_expr) => Some(&mut unary_expr.attrs),
412        Expr::Binary(binary_expr) => Some(&mut binary_expr.attrs),
413        Expr::Const(const_expr) => Some(&mut const_expr.attrs),
414        _ => None
415    }
416}