easy_macros_anyhow_result/
lib.rs1use 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 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 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 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 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 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 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 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 let wrapper_attrs = our_func.attrs.iter().enumerate().filter_map(|(i, attr)| {
166 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 result.into()
189}