function_timer_macro/
lib.rs1use proc_macro::TokenStream;
3use proc_macro2::Span;
4use quote::quote;
5use syn::fold::Fold;
6use syn::parse::{Parse, ParseStream};
7use syn::spanned::Spanned;
8use syn::token::Impl;
9use syn::{
10 parse_macro_input, Attribute, Block, Expr, ExprBlock, Ident, ImplItem, ImplItemFn, ItemFn,
11 ItemImpl, LitStr, Meta, Stmt, Type,
12};
13
14mod custom_keywords {
15 syn::custom_keyword!(disable);
16}
17
18enum Name {
19 Literal(LitStr),
20 Ident(Ident),
21 Disable(custom_keywords::disable),
22}
23
24impl Name {
25 fn disable(&self) -> bool {
26 matches!(self, Name::Disable(_))
27 }
28
29 fn span(&self) -> Span {
30 match self {
31 Self::Literal(lit) => lit.span(),
32 Self::Ident(ident) => ident.span(),
33 Self::Disable(tok) => tok.span(),
34 }
35 }
36}
37
38struct MetricName {
39 struct_name: Option<String>,
40 name: Name,
41}
42
43impl MetricName {
44 fn any_time_attribut(attributs: &[Attribute]) -> bool {
46 attributs.iter().any(|attr| {
47 if let Meta::Path(path) = &attr.meta {
48 path.segments.last().map(|i| i.ident.to_string()) == Some("time".to_string())
49 } else if let Meta::List(list) = &attr.meta {
50 list.path.segments.last().map(|i| i.ident.to_string()) == Some("time".to_string())
51 } else {
52 false
53 }
54 })
55 }
56
57 fn block_from(&self, block: Block, function_name: String) -> Block {
58 let metric_name = match &self.name {
59 Name::Literal(lit) => quote!(#lit),
60 Name::Ident(ident) => quote!(#ident),
61 Name::Disable(_) => return block,
63 };
64 let st = self.struct_name.clone();
65
66 let macro_stmt = if let Some(st) = st {
67 quote!(
68 let _guard = function_timer::FunctionTimer::new(#metric_name, Some(#st), #function_name);
69 )
70 } else {
71 quote!(
72 let _guard = function_timer::FunctionTimer::new(#metric_name, None, #function_name);
73 )
74 };
75 let mut statements: Vec<Stmt> = Vec::with_capacity(2);
76
77 let macro_stmt: Stmt = syn::parse2(macro_stmt).expect("Can't parse token");
78 statements.push(macro_stmt);
79
80 statements.push(Stmt::Expr(
81 Expr::Block(ExprBlock {
82 attrs: vec![],
83 label: None,
84 block,
85 }),
86 None,
87 ));
88
89 Block {
90 brace_token: Default::default(),
91 stmts: statements,
92 }
93 }
94}
95
96impl Parse for MetricName {
97 fn parse(input: ParseStream) -> syn::Result<Self> {
98 let lookahead = input.lookahead1();
99 let name =
100 if lookahead.peek(custom_keywords::disable) {
101 Name::Disable(input.parse::<custom_keywords::disable>()?)
102 } else if lookahead.peek(LitStr) {
103 Name::Literal(input.parse()?)
104 } else {
105 Name::Ident(input.parse().map_err(|error| {
106 syn::Error::new(error.span(), "Expected literal or identifier")
107 })?)
108 };
109
110 Ok(Self {
111 struct_name: None,
112 name,
113 })
114 }
115}
116
117impl Fold for MetricName {
118 fn fold_impl_item_fn(&mut self, i: ImplItemFn) -> ImplItemFn {
119 if Self::any_time_attribut(&i.attrs) {
122 return i;
123 }
124
125 let mut result = i.clone();
126 let block = i.block;
127 let name = i.sig.ident.to_string();
128 let new_block = self.block_from(block, name);
129 result.block = new_block;
130
131 result
132 }
133
134 fn fold_item_fn(&mut self, i: ItemFn) -> ItemFn {
135 let block = *i.block;
136 let name = i.sig.ident.to_string();
137
138 let new_block = self.block_from(block, name);
139
140 ItemFn {
141 attrs: i.attrs,
142 vis: i.vis,
143 sig: i.sig,
144 block: Box::new(new_block),
145 }
146 }
147 fn fold_item_impl(&mut self, i: ItemImpl) -> ItemImpl {
148 let mut new_items: Vec<ImplItem> = Vec::with_capacity(i.items.len());
149 let mut result = i.clone();
150 if let Type::Path(p) = *i.self_ty {
151 self.struct_name = p.path.segments.last().map(|p| p.ident.to_string());
152 }
153 for item in i.items {
154 if let ImplItem::Fn(method) = item {
155 new_items.push(ImplItem::Fn(self.fold_impl_item_fn(method)));
156 } else {
157 new_items.push(item);
158 }
159 }
160 result.items = new_items;
161 result
162 }
163}
164
165enum ImplOrFn {
166 Function(ItemFn),
167 ImplStruct(ItemImpl),
168}
169
170impl ImplOrFn {
171 fn is_impl(&self) -> bool {
172 matches!(self, ImplOrFn::ImplStruct(_))
173 }
174}
175
176impl Parse for ImplOrFn {
177 fn parse(input: ParseStream) -> syn::Result<Self> {
178 let attrs = input.call(Attribute::parse_outer)?;
179 if input.peek(Impl) {
180 let mut item: ItemImpl = input.parse()?;
181 item.attrs = attrs;
182 Ok(Self::ImplStruct(item))
183 } else {
184 let mut item: ItemFn = input.parse()?;
185 item.attrs = attrs;
186 Ok(Self::Function(item))
187 }
188 }
189}
190
191#[proc_macro_attribute]
197pub fn time(attr: TokenStream, item: TokenStream) -> TokenStream {
198 let mut args = parse_macro_input!(attr as MetricName);
199 let input = parse_macro_input!(item as ImplOrFn);
200
201 if args.name.disable() && input.is_impl() {
202 return syn::Error::new(args.name.span(), "You can't disable a whole impl block")
203 .into_compile_error()
204 .into();
205 }
206
207 match input {
208 ImplOrFn::Function(item_fn) => {
209 let output = args.fold_item_fn(item_fn);
210 TokenStream::from(quote!(#output))
211 }
212 ImplOrFn::ImplStruct(impl_struct) => {
213 let output = args.fold_item_impl(impl_struct);
214 TokenStream::from(quote!(#output))
215 }
216 }
217}