errata_macros/
lib.rs

1use proc_macro::TokenStream;
2
3use syn::{parse_macro_input, ItemFn};
4
5/// Sets up nice error handling for your Rust function.
6///
7/// Technically, this can be applied to any function. However, you only need
8/// to apply it to your `main` function. It should never be used in libraries
9/// (that applies to the whole crate).
10///
11/// If you forget this, your pretty errors will all show up as `Box<dyn Any>`.
12#[proc_macro_attribute]
13pub fn catch(_attr: TokenStream, item: TokenStream) -> TokenStream {
14    let input = parse_macro_input!(item as ItemFn);
15    let sig = input.sig;
16    let block = input.block;
17
18    TokenStream::from(quote::quote! {
19        #sig {
20            let (__errata_tx, __errata_rx) = std::sync::mpsc::channel();
21
22            let __errata_old_hook = std::panic::take_hook();
23            std::panic::set_hook(Box::new(move |info| {
24                let location = match info.location() {
25                    Some(l) => format!(" (at {}:{}:{})", l.file(), l.line(), l.column()),
26                    None => String::new(),
27                };
28
29                let _ = __errata_tx.send(location);
30            }));
31
32            let __errata_result = std::panic::catch_unwind(|| {
33                #block
34            });
35
36            match __errata_result {
37                Ok(_) => return,
38                Err(e) => {
39                    let location = match __errata_rx.recv() {
40                        Ok(l) => l,
41                        Err(e) => {
42                            eprintln!("internal errata error (failed to recieve location): {e}");
43                            String::new()
44                        }
45                    };
46
47                    if let Some(e) = e.downcast_ref::<errata::ErrataPanic>() {
48                        eprintln!("{e}");
49                    } else if let Some(e) = e.downcast_ref::<&str>() {
50                        eprintln!("error{location}: {e}");
51
52                        let bt = std::backtrace::Backtrace::capture();
53
54                        use std::backtrace::BacktraceStatus as BtS;
55                        match bt.status() {
56                            BtS::Captured => eprintln!("{bt}"),
57                            BtS::Disabled => eprintln!("run with `RUST_BACKTRACE=1` environment variable to display a backtrace"),
58                            _ => {}
59                        }
60                    } else if let Some(e) = e.downcast_ref::<String>() {
61                        eprintln!("error{location}: {e}");
62
63                        let bt = std::backtrace::Backtrace::capture();
64
65                        use std::backtrace::BacktraceStatus as BtS;
66                        match bt.status() {
67                            BtS::Captured => eprintln!("{bt}"),
68                            BtS::Disabled => eprintln!("run with `RUST_BACKTRACE=1` environment variable to display a backtrace"),
69                            _ => {}
70                        }
71                    } else {
72                        eprintln!("Unhandled error(location)");
73                        let bt = std::backtrace::Backtrace::capture();
74
75                        use std::backtrace::BacktraceStatus as BtS;
76                        match bt.status() {
77                            BtS::Captured => eprintln!("{bt}"),
78                            BtS::Disabled => eprintln!("run with `RUST_BACKTRACE=1` environment variable to display a backtrace"),
79                            _ => {}
80                        }
81                    }
82
83                    std::process::exit(1);
84                }
85            }
86        }
87    })
88}