1#![feature(extern_crate_item_prelude)]
171#![feature(proc_macro_diagnostic)]
172#![recursion_limit="128"]
173extern crate proc_macro;
174
175use proc_macro::TokenStream;
176use syn;
177use syn::{Token, parse_quote};
178use syn::spanned::Spanned;
179use syn::punctuated::Punctuated;
180use quote::quote;
181use proc_macro2;
182
183mod config;
184mod error;
185
186use self::error::{DiagnosticError, Result};
187use syn::parse::Parse;
188use syn::parse::ParseStream;
189use syn::parse_macro_input;
190
191struct Attr {
192 cache_type: syn::Type,
193 cache_creation_expr: syn::Expr,
194}
195
196impl Parse for Attr {
197 fn parse(input: ParseStream) -> syn::parse::Result<Self> {
198 let cache_type: syn::Type = input.parse()?;
199 input.parse::<Token![:]>()?;
200 let cache_creation_expr: syn::Expr = input.parse()?;
201 Ok(Attr {
202 cache_type,
203 cache_creation_expr,
204 })
205 }
206}
207
208#[proc_macro_attribute]
210pub fn cache(attr: TokenStream, item: TokenStream) -> TokenStream {
211 let attr = parse_macro_input!(attr as Attr);
212
213 match lru_cache_impl(attr, item.clone()) {
214 Ok(tokens) => return tokens,
215 Err(e) => {
216 e.emit();
217 return item;
218 }
219 }
220}
221
222fn lru_cache_impl(attr: Attr, item: TokenStream) -> Result<TokenStream> {
224 let mut original_fn: syn::ItemFn = match syn::parse(item.clone()) {
225 Ok(ast) => ast,
226 Err(e) => {
227 let diag = proc_macro2::Span::call_site().unstable()
228 .error("lru_cache may only be used on functions");
229 return Err(DiagnosticError::new_with_syn_error(diag, e));
230 }
231 };
232
233 let (macro_config, out_attributes) =
234 {
235 let attribs = &original_fn.attrs[..];
236 config::Config::parse_from_attributes(attribs)?
237 };
238 original_fn.attrs = out_attributes;
239
240 let mut new_fn = original_fn.clone();
241
242 let return_type = get_cache_fn_return_type(&original_fn)?;
243
244 let new_name = format!("__lru_base_{}", original_fn.ident.to_string());
245 original_fn.ident = syn::Ident::new(&new_name[..], original_fn.ident.span());
246
247 let (call_args, types, cache_args) = get_args_and_types(&original_fn, ¯o_config)?;
248 let cloned_args = make_cloned_args_tuple(&cache_args);
249 let fn_path = path_from_ident(original_fn.ident.clone());
250
251 let fn_call = syn::ExprCall {
252 attrs: Vec::new(),
253 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
254 args: call_args.clone(),
255 func: Box::new(fn_path)
256 };
257
258 let tuple_type = syn::TypeTuple {
259 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
260 elems: types,
261 };
262
263 let cache_type = &attr.cache_type;
264 let cache_type_with_generics: syn::Type = parse_quote! {
265 #cache_type<#tuple_type, #return_type>
266 };
267
268 let lru_body = build_cache_body(&cache_type_with_generics, &attr.cache_creation_expr, &cloned_args,
269 &fn_call, ¯o_config);
270
271
272 new_fn.block = Box::new(lru_body);
273
274 let out = quote! {
275 #original_fn
276
277 #new_fn
278 };
279 Ok(out.into())
280}
281
282fn build_cache_body(full_cache_type: &syn::Type, cache_new: &syn::Expr,
284 cloned_args: &syn::ExprTuple, inner_fn_call: &syn::ExprCall,
285 config: &config::Config) -> syn::Block
286{
287 if config.use_tls {
288 build_tls_cache_body(full_cache_type, cache_new, cloned_args, inner_fn_call)
289 } else {
290 build_mutex_cache_body(full_cache_type, cache_new, cloned_args, inner_fn_call)
291 }
292}
293
294fn build_tls_cache_body(full_cache_type: &syn::Type, cache_new: &syn::Expr,
296 cloned_args: &syn::ExprTuple, inner_fn_call: &syn::ExprCall) -> syn::Block
297{
298 parse_quote! {
299 {
300 use std::cell::RefCell;
301 use std::thread_local;
302 thread_local!(
303 static cache: RefCell<#full_cache_type> =
304 RefCell::new(#cache_new);
305 );
306 cache.with(|c| {
307 let mut cache_ref = c.borrow_mut();
308 let cloned_args = #cloned_args;
309
310 let stored_result = cache_ref.get_mut(&cloned_args);
311 if let Some(stored_result) = stored_result {
312 return stored_result.clone()
313 }
314
315 drop(cache_ref);
318
319 let ret = #inner_fn_call;
320 c.borrow_mut().insert(cloned_args, ret.clone());
321 ret
322 })
323 }
324 }
325}
326
327fn build_mutex_cache_body(full_cache_type: &syn::Type, cache_new: &syn::Expr,
329 cloned_args: &syn::ExprTuple, inner_fn_call: &syn::ExprCall) -> syn::Block
330{
331 parse_quote! {
332 {
333 use lazy_static::lazy_static;
334 use std::sync::Mutex;
335
336 lazy_static! {
337 static ref cache: Mutex<#full_cache_type> =
338 Mutex::new(#cache_new);
339 }
340
341 let cloned_args = #cloned_args;
342
343 let mut cache_unlocked = cache.lock().unwrap();
344 let stored_result = cache_unlocked.get_mut(&cloned_args);
345 if let Some(stored_result) = stored_result {
346 return stored_result.clone();
347 };
348
349 drop(cache_unlocked);
351
352 let ret = #inner_fn_call;
353 let mut cache_unlocked = cache.lock().unwrap();
354 cache_unlocked.insert(cloned_args, ret.clone());
355 ret
356 }
357 }
358}
359
360fn get_cache_fn_return_type(original_fn: &syn::ItemFn) -> Result<Box<syn::Type>> {
361 if let syn::ReturnType::Type(_, ref ty) = original_fn.decl.output {
362 Ok(ty.clone())
363 } else {
364 let diag = original_fn.ident.span().unstable()
365 .error("There's no point of caching the output of a function that has no output");
366 return Err(DiagnosticError::new(diag));
367 }
368}
369
370fn path_from_ident(ident: syn::Ident) -> syn::Expr {
371 let mut segments: Punctuated<_, Token![::]> = Punctuated::new();
372 segments.push(syn::PathSegment { ident: ident, arguments: syn::PathArguments::None });
373 syn::Expr::Path(syn::ExprPath { attrs: Vec::new(), qself: None, path: syn::Path { leading_colon: None, segments: segments} })
374}
375
376fn make_cloned_args_tuple(args: &Punctuated<syn::Expr, Token![,]>) -> syn::ExprTuple {
377 let mut cloned_args = Punctuated::<_, Token![,]>::new();
378 for arg in args {
379 let call = syn::ExprMethodCall {
380 attrs: Vec::new(),
381 receiver: Box::new(arg.clone()),
382 dot_token: syn::token::Dot { spans: [arg.span(); 1] },
383 method: syn::Ident::new("clone", proc_macro2::Span::call_site()),
384 turbofish: None,
385 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
386 args: Punctuated::new(),
387 };
388 cloned_args.push(syn::Expr::MethodCall(call));
389 }
390 syn::ExprTuple {
391 attrs: Vec::new(),
392 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
393 elems: cloned_args,
394 }
395}
396
397fn get_args_and_types(f: &syn::ItemFn, config: &config::Config) ->
398 Result<(Punctuated<syn::Expr, Token![,]>, Punctuated<syn::Type, Token![,]>, Punctuated<syn::Expr, Token![,]>)>
399{
400 let mut call_args = Punctuated::<_, Token![,]>::new();
401 let mut types = Punctuated::<_, Token![,]>::new();
402 let mut cache_args = Punctuated::<_, Token![,]>::new();
403
404 for input in &f.decl.inputs {
405 match input {
406 syn::FnArg::SelfValue(p) => {
407 let diag = p.span().unstable()
408 .error("`self` arguments are currently unsupported by lru_cache");
409 return Err(DiagnosticError::new(diag));
410 }
411 syn::FnArg::SelfRef(p) => {
412 let diag = p.span().unstable()
413 .error("`&self` arguments are currently unsupported by lru_cache");
414 return Err(DiagnosticError::new(diag));
415 }
416 syn::FnArg::Captured(arg_captured) => {
417 let mut segments: syn::punctuated::Punctuated<_, Token![::]> = syn::punctuated::Punctuated::new();
418 let arg_name;
419 if let syn::Pat::Ident(ref pat_ident) = arg_captured.pat {
420 arg_name = pat_ident.ident.clone();
421 if let Some(m) = pat_ident.mutability {
422 if !config.ignore_args.contains(&arg_name) {
423 let diag = m.span.unstable()
424 .error("`mut` arguments are not supported with lru_cache as this could lead to incorrect results being stored");
425 return Err(DiagnosticError::new(diag));
426 }
427 }
428 segments.push(syn::PathSegment { ident: pat_ident.ident.clone(), arguments: syn::PathArguments::None });
429 } else {
430 let diag = arg_captured.span().unstable()
431 .error("unsupported argument kind");
432 return Err(DiagnosticError::new(diag));
433 }
434
435 let arg_path = syn::Expr::Path(syn::ExprPath { attrs: Vec::new(), qself: None, path: syn::Path { leading_colon: None, segments } });
436
437 if !config.ignore_args.contains(&arg_name) {
438
439 if let syn::Type::Reference(type_reference) = &arg_captured.ty {
441 types.push(type_reference.elem.as_ref().to_owned()); } else {
443 types.push(arg_captured.ty.clone());
444 }
445
446 cache_args.push(arg_path.clone());
447 }
448
449
450 call_args.push(arg_path);
451 },
452 syn::FnArg::Inferred(p) => {
453 let diag = p.span().unstable()
454 .error("inferred arguments are currently unsupported by lru_cache");
455 return Err(DiagnosticError::new(diag));
456 }
457 syn::FnArg::Ignored(p) => {
458 let diag = p.span().unstable()
459 .error("ignored arguments are currently unsupported by lru_cache");
460 return Err(DiagnosticError::new(diag));
461 }
462 }
463 }
464
465 if types.len() == 1 {
466 types.push_punct(syn::token::Comma { spans: [proc_macro2::Span::call_site(); 1] })
467 }
468
469 Ok((call_args, types, cache_args))
470}