Skip to main content

easy_macros_anyhow_result/
lib.rs

1use helpers::find_crate;
2use proc_macro::TokenStream;
3use quote::{ToTokens, quote};
4
5fn crate_missing_panic(crate_name: &str) -> ! {
6    panic!(
7        "Using anyhow-result requires `{crate_name}` crate to be present in dependencies! You can add it with `{crate_name} = \"*\"` in your Cargo.toml dependencies or with `cargo add {crate_name}` command."
8    );
9}
10
11fn quote_crate() -> proc_macro2::TokenStream {
12    if let Some(found) = find_crate("quote", quote! {}) {
13        found
14    } else {
15        crate_missing_panic("quote");
16    }
17}
18
19fn syn_crate() -> proc_macro2::TokenStream {
20    if let Some(found) = find_crate("syn", quote! {}) {
21        found
22    } else {
23        crate_missing_panic("syn");
24    }
25}
26
27fn proc_macro2_crate() -> proc_macro2::TokenStream {
28    if let Some(found) = find_crate("proc-macro2", quote! {}) {
29        found
30    } else {
31        crate_missing_panic("proc-macro2");
32    }
33}
34
35fn anyhow_crate() -> proc_macro2::TokenStream {
36    if let Some(found) = find_crate("anyhow", quote! {}) {
37        found
38    } else {
39        crate_missing_panic("anyhow");
40    }
41}
42
43#[proc_macro_attribute]
44pub fn anyhow_result(_attr: TokenStream, item: TokenStream) -> TokenStream {
45    let mut our_func = syn::parse_macro_input!(item as syn::ItemFn);
46
47    // let parent_crate = external_crate_parent();
48    let quote_crate = quote_crate();
49    let syn_crate = syn_crate();
50    let proc_macro2_crate = proc_macro2_crate();
51    let anyhow_crate = anyhow_crate()
52        .to_string()
53        .replace(|c: char| c.is_whitespace(), "");
54
55    //Check if output of our function is a anyhow::Result<TokenStream>
56    let func_output = &our_func.sig.output;
57    match func_output {
58        syn::ReturnType::Default => {
59            panic!("Function must return a {anyhow_crate}::Result<TokenStream>",)
60        }
61        syn::ReturnType::Type(_, ty) => {
62            let ty_str = ty
63                .to_token_stream()
64                .to_string()
65                .replace(|c: char| c.is_whitespace(), "");
66            if ty_str != format!("{anyhow_crate}::Result<TokenStream>",)
67                && ty_str != format!("{anyhow_crate}::Result<proc_macro::TokenStream>",)
68            {
69                panic!("Function must return a {anyhow_crate}::Result<TokenStream>",);
70            }
71        }
72    }
73
74    let func_name = &our_func.sig.ident;
75
76    let inputs = &our_func.sig.inputs;
77    //inputs as arguments to function call
78    let inputs_passed_in = inputs.iter().enumerate().map(|(index, arg)| match arg {
79        syn::FnArg::Typed(arg) => {
80            let pat = &arg.pat;
81            if index == 1 {
82                //Clone our item
83                quote::quote! {
84                    #pat.clone(),
85                }
86            } else {
87                quote::quote! {
88                    #pat,
89                }
90            }
91        }
92        _ => panic!("Self arguments shouldn't be supported on procedural macros"),
93    });
94
95    //If our function has #[proc_macro] attribute return nothing on error
96    //If our function has #[proc_macro_derive] attribute return nothing on error
97    //If our function has #[proc_macro_attribute] attribute return back item (second argument) on error
98    //If our function has neither of those attributes panic
99    let (err_result, macro_attr, attr_index) = {
100        let mut err_result = None;
101        let mut macro_attr = None;
102        let mut attr_index = None;
103        for (index, attr) in our_func.attrs.iter().enumerate() {
104            let attr_name = attr.path().to_token_stream().to_string();
105            if attr_name == "proc_macro" || attr_name == "proc_macro_derive" {
106                err_result = Some(quote::quote! {
107                let formatted_error = format!("{:?}", ___macro_err);
108                let mut result=#quote_crate::quote! {compile_error!};
109
110                //Adds (formatted_error) to the end of the result
111                result.extend( #proc_macro2_crate::TokenStream::from(#proc_macro2_crate::TokenTree::Group(#proc_macro2_crate::Group::new(
112                    #proc_macro2_crate::Delimiter::Parenthesis,
113                    #syn_crate::LitStr::new(&formatted_error, #proc_macro2_crate::Span::call_site()).into_token_stream(),
114                ))));
115
116                result.extend(#quote_crate::quote! {;});
117
118                result });
119                macro_attr = Some(attr.clone());
120                attr_index = Some(index);
121                break;
122            } else if attr_name == "proc_macro_attribute" {
123                let second_input_arg = if let Some(arg) = inputs.iter().nth(1) {
124                    if let syn::FnArg::Typed(arg) = arg {
125                        arg.pat.clone()
126                    } else {
127                        panic!("Expected a typed argument");
128                    }
129                } else {
130                    panic!("proc_macro_attribute function must have two arguments");
131                };
132                err_result = Some(quote::quote! {
133                    let formatted_error= format!("{:?}", ___macro_err);
134                    let mut result = #quote_crate::quote! {compile_error!};
135
136                    //Adds (formatted_error) to the end of the result
137                    result.extend( #proc_macro2_crate::TokenStream::from(#proc_macro2_crate::TokenTree::Group(#proc_macro2_crate::Group::new(
138                        #proc_macro2_crate::Delimiter::Parenthesis,
139                        #syn_crate::LitStr::new(&formatted_error, #proc_macro2_crate::Span::call_site()).into_token_stream(),
140                    ))));
141
142                    result.extend(#quote_crate::quote! {;});
143
144                    result.extend(#proc_macro2_crate::TokenStream::from(#second_input_arg));
145                    result
146                });
147                macro_attr = Some(attr.clone());
148                attr_index = Some(index);
149                break;
150            }
151        }
152        match (err_result, macro_attr, attr_index) {
153            (Some(err_result), Some(macro_attr), Some(attr_index)) => {
154                (err_result, macro_attr, attr_index)
155            }
156            _ => panic!(
157                "Function must have either #[proc_macro] or #[proc_macro_derive] or #[proc_macro_attribute] attribute!"
158            ),
159        }
160    };
161
162    our_func.attrs.remove(attr_index);
163
164    // Extract doc comments and other attributes to copy to the wrapper, excluding the proc_macro attribute
165    let wrapper_attrs = our_func.attrs.iter().enumerate().filter_map(|(i, attr)| {
166        // Skip the attribute we just removed (adjust index since we already removed one)
167        if i == attr_index { None } else { Some(attr) }
168    });
169
170    let result = quote::quote! {
171        #(#wrapper_attrs)*
172        #macro_attr
173        pub fn #func_name(#inputs) -> proc_macro::TokenStream {
174
175            use #quote_crate::ToTokens;
176
177            #our_func
178
179            match #func_name(#(#inputs_passed_in)*) {
180                Ok(value) => value,
181                Err(___macro_err) => {#err_result .into()},
182            }
183        }
184    };
185
186    // panic!("{}", result.to_string());
187
188    result.into()
189}