1#![allow(
4 clippy::enum_glob_use,
5 clippy::match_bool,
6 clippy::if_not_else,
7 clippy::module_name_repetitions,
8 clippy::needless_pass_by_value,
9 clippy::implicit_clone
10)]
11
12use proc_macro::TokenStream;
13use quote::{format_ident, quote, quote_spanned, ToTokens};
14use syn::{
15 fold::Fold, parse_macro_input, parse_quote, spanned::Spanned, FnArg, ItemFn, Pat, ReturnType,
16 Type, Visibility,
17};
18
19mod add_error_info;
20use self::add_error_info::AddErrorInfo;
21
22mod options;
23use self::options::Options;
24
25mod log_statement;
26use self::log_statement::build_log_statement;
27
28#[proc_macro_attribute]
29#[allow(clippy::too_many_lines)]
30pub fn wrap_match(args: TokenStream, input: TokenStream) -> TokenStream {
32 let mut options = parse_macro_input!(args as Options);
33 let input = parse_macro_input!(input as ItemFn);
34
35 if match input.sig.output {
36 ReturnType::Default => true,
37 ReturnType::Type(_, ref ty) => match &**ty {
38 Type::Path(p) => p
39 .path
40 .segments
41 .last()
42 .map_or(true, |s| !s.ident.to_string().contains("Result")),
43 _ => true,
44 },
45 } {
46 let span = if let ReturnType::Type(_, t) = &input.sig.output {
47 t.span()
48 } else {
49 input.sig.span()
50 };
51 return quote_spanned! {span=>
52 compile_error!("wrap_match currently only supports functions that return `Result`s");
53 }
54 .into();
55 }
56
57 if let Some(constness) = &input.sig.constness {
58 return quote_spanned! {constness.span()=>
59 compile_error!("wrap_match cannot be used on const functions because the log crate cannot be used in const contexts");
60 }
61 .into();
62 }
63
64 let mut has_self_argument = false;
65 let mut args_without_types = vec![];
67 let mut args_without_types_including_self = vec![];
68 for arg in &input.sig.inputs {
69 match arg {
70 FnArg::Receiver(_) => {
71 has_self_argument = true;
72 args_without_types_including_self.push(quote!(self));
73 }
74 FnArg::Typed(arg) => {
75 let tokens = if let Pat::Ident(mut a) = *arg.pat.clone() {
76 a.attrs.clear();
77 a.mutability = None;
78 a.into_token_stream()
79 } else {
80 arg.pat.clone().into_token_stream()
81 };
82 args_without_types.push(tokens.clone());
83 args_without_types_including_self.push(tokens);
84 }
85 }
86 }
87
88 let self_dot = if has_self_argument {
89 quote!(self.)
90 } else {
91 quote!()
92 };
93
94 let asyncness_await = match input.sig.asyncness {
95 Some(_) => quote!(.await),
96 None => quote!(),
97 };
98
99 let attrs = input.attrs.clone();
100 let vis = input.vis.clone();
101 let mut sig = input.sig.clone();
102 if options.disregard_result {
103 sig.output = ReturnType::Default;
104 }
105
106 let orig_name = input.sig.ident.clone();
107 options.replace_function_in_messages(orig_name.to_string());
108 let inner_name = format_ident!("_wrap_match_inner_{}", orig_name);
109
110 let mut input = AddErrorInfo.fold_item_fn(input);
111 input.sig.ident = inner_name.clone();
112 input.vis = Visibility::Inherited; input.attrs = vec![
114 parse_quote!(#[doc(hidden)]),
117 parse_quote!(#[deprecated = "inner function for wrap-match. Please do not use!"]),
118 parse_quote!(#[inline(always)]), ];
120
121 let log_success = if options.log_success {
122 Some(build_log_statement(
123 &options.success_message,
124 &[],
125 &args_without_types_including_self,
126 quote!(info),
127 ))
128 } else {
129 None
130 };
131
132 let log_error = build_log_statement(
133 &options.error_message,
134 &[
135 ("line", quote!(_line)),
136 ("expr", quote!(_expr)),
137 ("error", quote!(e.inner)),
138 ],
139 &args_without_types_including_self,
140 quote!(error),
141 );
142
143 let log_error_without_info = build_log_statement(
144 &options.error_message_without_info,
145 &[("error", quote!(e.inner))],
146 &args_without_types_including_self,
147 quote!(error),
148 );
149
150 let ok = if !options.disregard_result {
151 quote!(Ok(r))
152 } else {
153 quote!()
154 };
155 let err = if !options.disregard_result {
156 quote!(Err(e.inner))
157 } else {
158 quote!()
159 };
160
161 let (outer_input, inner_input) = if has_self_argument {
163 (Some(input), None)
164 } else {
165 (None, Some(input))
166 };
167
168 quote! {
169 #outer_input
170
171 #(#attrs)* #vis #sig {
172 #inner_input
173
174 #[allow(deprecated)]
175 match #self_dot #inner_name(#(#args_without_types),*) #asyncness_await {
176 Ok(r) => {
177 #log_success
178 #ok
179 }
180 Err(e) => {
181 if let Some((_line, _expr)) = e.line_and_expr {
182 #log_error
183 } else {
184 #log_error_without_info
185 }
186 #err
187 }
188 }
189 }
190 }
191 .into()
192}