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 modifiers: i.modifiers,
145 block: Box::new(new_block),
146 }
147 }
148 fn fold_item_impl(&mut self, i: ItemImpl) -> ItemImpl {
149 let mut new_items: Vec<ImplItem> = Vec::with_capacity(i.items.len());
150 let mut result = i.clone();
151 if let Type::Path(p) = *i.self_ty {
152 self.struct_name = p.path.segments.last().map(|p| p.ident.to_string());
153 }
154 for item in i.items {
155 if let ImplItem::Fn(method) = item {
156 new_items.push(ImplItem::Fn(self.fold_impl_item_fn(method)));
157 } else {
158 new_items.push(item);
159 }
160 }
161 result.items = new_items;
162 result
163 }
164}
165
166enum ImplOrFn {
167 Function(ItemFn),
168 ImplStruct(ItemImpl),
169}
170
171impl ImplOrFn {
172 fn is_impl(&self) -> bool {
173 matches!(self, ImplOrFn::ImplStruct(_))
174 }
175}
176
177impl Parse for ImplOrFn {
178 fn parse(input: ParseStream) -> syn::Result<Self> {
179 let attrs = input.call(Attribute::parse_outer)?;
180 if input.peek(Impl) {
181 let mut item: ItemImpl = input.parse()?;
182 item.attrs = attrs;
183 Ok(Self::ImplStruct(item))
184 } else {
185 let mut item: ItemFn = input.parse()?;
186 item.attrs = attrs;
187 Ok(Self::Function(item))
188 }
189 }
190}
191
192#[proc_macro_attribute]
198pub fn time(attr: TokenStream, item: TokenStream) -> TokenStream {
199 let mut args = parse_macro_input!(attr as MetricName);
200 let input = parse_macro_input!(item as ImplOrFn);
201
202 if args.name.disable() && input.is_impl() {
203 return syn::Error::new(args.name.span(), "You can't disable a whole impl block")
204 .into_compile_error()
205 .into();
206 }
207
208 match input {
209 ImplOrFn::Function(item_fn) => {
210 let output = args.fold_item_fn(item_fn);
211 TokenStream::from(quote!(#output))
212 }
213 ImplOrFn::ImplStruct(impl_struct) => {
214 let output = args.fold_item_impl(impl_struct);
215 TokenStream::from(quote!(#output))
216 }
217 }
218}