lru_cache_macros/lib.rs
1//! lru-cache-macros
2//! ================
3//!
4//! An attribute procedural macro to automatically cache the result of a function given a set of inputs.
5//!
6//! **This crate has been deprecated in favor of [cache-macro](https://crates.io/crates/cache-macro) which maintains
7//! the same functionality, but supports more than just lru caches. New code should use that crate instead.**
8//!
9//! # Example:
10//!
11//! ```rust
12//! use lru_cache_macros::lru_cache;
13//!
14//! #[lru_cache(20)]
15//! fn fib(x: u32) -> u64 {
16//! println!("{:?}", x);
17//! if x <= 1 {
18//! 1
19//! } else {
20//! fib(x - 1) + fib(x - 2)
21//! }
22//! }
23//!
24//! assert_eq!(fib(19), 6765);
25//! ```
26//!
27//! The above example only calls `fib` twenty times, with the values from 0 to 19. All intermediate
28//! results because of the recursion hit the cache.
29//!
30//! # Usage:
31//!
32//! Simply place `#[lru_cache([size])]` above your function. The function must obey a few properties
33//! to use lru_cache:
34//!
35//! * All arguments and return values must implement `Clone`.
36//! * The function may not take `self` in any form.
37//!
38//! The macro will use the LruCache at `::lru_cache::LruCache` by default. This can be changed by
39//! setting the `cache_type` config variable as shown in the configuration section.
40//!
41//! The `LruCache` type used must accept two generic parameters `<Args, Return>` and must support methods
42//! `get_mut(&K)` and `insert(K, V)`. The `lru-cache` crate meets these requirements.
43//!
44//! Currently, this crate only works on nightly rust. However, once the 2018 edition stabilizes as well as the
45//! procedural macro diagnostic interface, it should be able to run on stable.
46//!
47//! # Configuration:
48//!
49//! The lru_cache macro can be configured by adding additional attributes under `#[lru_cache(size)]`.
50//!
51//! All configuration attributes take the form `#[lru_config(...)]`. The available attributes are:
52//!
53//! * `#[lru_config(cache_type = ...)]`
54//!
55//! This allows the cache type used internally to be changed. The default is equivalent to
56//!
57//! ```#[lru_config(cache_type = ::lru_cache::LruCache)]```
58//!
59//! * `#[lru_config(ignore_args = ...)]`
60//!
61//! This allows certain arguments to be ignored for the purposes of caching. That means they are not part of the
62//! hash table key and thus should never influence the output of the function. It can be useful for diagnostic settings,
63//! returning the number of times executed, or other introspection purposes.
64//!
65//! `ignore_args` takes a comma-separated list of variable identifiers to ignore.
66//!
67//! ### Example:
68//! ```rust
69//! use lru_cache_macros::lru_cache;
70//! #[lru_cache(20)]
71//! #[lru_config(ignore_args = call_count)]
72//! fn fib(x: u64, call_count: &mut u32) -> u64 {
73//! *call_count += 1;
74//! if x <= 1 {
75//! 1
76//! } else {
77//! fib(x - 1, call_count) + fib(x - 2, call_count)
78//! }
79//! }
80//!
81//! let mut call_count = 0;
82//! assert_eq!(fib(39, &mut call_count), 102_334_155);
83//! assert_eq!(call_count, 40);
84//! ```
85//!
86//! The `call_count` argument can vary, caching is only done based on `x`.
87//!
88//! * `#[lru_config(thread_local)]`
89//!
90//! Store the cache in thread-local storage instead of global static storage. This avoids the overhead of Mutex locking,
91//! but each thread will be given its own cache, and all caching will not affect any other thread.
92//!
93//! Expanding on the first example:
94//!
95//! ```rust
96//! use lru_cache_macros::lru_cache;
97//!
98//! #[lru_cache(20)]
99//! #[lru_config(thread_local)]
100//! fn fib(x: u32) -> u64 {
101//! println!("{:?}", x);
102//! if x <= 1 {
103//! 1
104//! } else {
105//! fib(x - 1) + fib(x - 2)
106//! }
107//! }
108//!
109//! assert_eq!(fib(19), 6765);
110//! ```
111//!
112//! # Details
113//! The created cache is stored as a static variable protected by a mutex unless the `#[lru_config(thread_local)]` configuration
114//! is added.
115//!
116//! With the default settings, the fibonacci example will generate the following code:
117//!
118//! ```rust
119//! fn __lru_base_fib(x: u32) -> u64 {
120//! if x <= 1 { 1 } else { fib(x - 1) + fib(x - 2) }
121//! }
122//! fn fib(x: u32) -> u64 {
123//! use lazy_static::lazy_static;
124//! use std::sync::Mutex;
125//!
126//! lazy_static! {
127//! static ref cache: Mutex<::lru_cache::LruCache<(u32,), u64>> =
128//! Mutex::new(::lru_cache::LruCache::new(20usize));
129//! }
130//!
131//! let cloned_args = (x.clone(),);
132//! let mut cache_unlocked = cache.lock().unwrap();
133//! let stored_result = cache_unlocked.get_mut(&cloned_args);
134//! if let Some(stored_result) = stored_result {
135//! return stored_result.clone();
136//! };
137//! drop(cache_unlocked);
138//! let ret = __lru_base_fib(x);
139//! let mut cache_unlocked = cache.lock().unwrap();
140//! cache_unlocked.insert(cloned_args, ret.clone());
141//! ret
142//! }
143//!
144//! ```
145//!
146//! Whereas, if you use the `#[lru_config(thread_local)]` the generated code will look like:
147//!
148//!
149//! ```rust
150//! fn __lru_base_fib(x: u32) -> u64 {
151//! if x <= 1 { 1 } else { fib(x - 1) + fib(x - 2) }
152//! }
153//! fn fib(x: u32) -> u64 {
154//! use std::cell::UnsafeCell;
155//! use std::thread_local;
156//!
157//! thread_local!(
158//! static cache: UnsafeCell<::lru_cache::LruCache<(u32,), u64>> =
159//! UnsafeCell::new(::lru_cache::LruCache::new(20usize));
160//! );
161//!
162//! cache.with(|c|
163//! {
164//! let mut cache_ref = unsafe { &mut *c.get() };
165//! let cloned_args = (x.clone(),);
166//! let stored_result = cache_ref.get_mut(&cloned_args);
167//! if let Some(stored_result) = stored_result {
168//! stored_result.clone()
169//! } else {
170//! let ret = __lru_base_fib(x);
171//! cache_ref.insert(cloned_args, ret.clone());
172//! ret
173//! }
174//! })
175//! }
176//! ```
177
178#![feature(extern_crate_item_prelude)]
179#![feature(proc_macro_diagnostic)]
180#![recursion_limit="128"]
181extern crate proc_macro;
182
183use std::result;
184
185use proc_macro::TokenStream;
186use syn;
187use syn::{Token, parse_quote};
188use syn::spanned::Spanned;
189use syn::punctuated::Punctuated;
190use quote::quote;
191use proc_macro2;
192
193mod config;
194mod error;
195
196use self::error::{DiagnosticError, Result};
197
198// Function shim to allow us to use `Result` and the `?` operator.
199#[proc_macro_attribute]
200pub fn lru_cache(attr: TokenStream, item: TokenStream) -> TokenStream {
201 match lru_cache_impl(attr, item.clone()) {
202 Ok(tokens) => return tokens,
203 Err(e) => {
204 e.emit();
205 return item;
206 }
207 }
208}
209
210// The main entry point for the macro.
211fn lru_cache_impl(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
212 let mut original_fn: syn::ItemFn = match syn::parse(item.clone()) {
213 Ok(ast) => ast,
214 Err(e) => {
215 let diag = proc_macro2::Span::call_site().unstable()
216 .error("lru_cache may only be used on functions");
217 return Err(DiagnosticError::new_with_syn_error(diag, e));
218 }
219 };
220
221 let (macro_config, out_attributes) =
222 {
223 let attribs = &original_fn.attrs[..];
224 config::Config::parse_from_attributes(attribs)?
225 };
226 original_fn.attrs = out_attributes;
227
228 let mut new_fn = original_fn.clone();
229
230 let cache_size = get_lru_size(attr)?;
231 let return_type = get_cache_fn_return_type(&original_fn)?;
232
233 let new_name = format!("__lru_base_{}", original_fn.ident.to_string());
234 original_fn.ident = syn::Ident::new(&new_name[..], original_fn.ident.span());
235
236 let (call_args, types, cache_args) = get_args_and_types(&original_fn, ¯o_config)?;
237 let cloned_args = make_cloned_args_tuple(&cache_args);
238 let fn_path = path_from_ident(original_fn.ident.clone());
239
240 let fn_call = syn::ExprCall {
241 attrs: Vec::new(),
242 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
243 args: call_args.clone(),
244 func: Box::new(fn_path)
245 };
246
247 let tuple_type = syn::TypeTuple {
248 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
249 elems: types,
250 };
251
252 // Build all common types needed for each body impl.
253 let cache_type = ¯o_config.cache_type;
254 let cache_type_with_generics: syn::Type = parse_quote! {
255 #cache_type<#tuple_type, #return_type>
256 };
257 let cache_new: syn::Expr = parse_quote! {
258 #cache_type::new(#cache_size)
259 };
260
261 let lru_body = build_cache_body(&cache_type_with_generics, &cache_new, &cloned_args,
262 &fn_call, ¯o_config);
263
264
265 new_fn.block = Box::new(lru_body);
266
267 let out = quote! {
268 #original_fn
269
270 #new_fn
271 };
272 Ok(out.into())
273}
274
275// Build the body of the caching function. What is constructed depends on the config value.
276fn build_cache_body(full_cache_type: &syn::Type, cache_new: &syn::Expr,
277 cloned_args: &syn::ExprTuple, inner_fn_call: &syn::ExprCall,
278 config: &config::Config) -> syn::Block
279{
280 if config.use_tls {
281 build_tls_cache_body(full_cache_type, cache_new, cloned_args, inner_fn_call)
282 } else {
283 build_mutex_cache_body(full_cache_type, cache_new, cloned_args, inner_fn_call)
284 }
285}
286
287// Build the body of the caching function which puts the cache in thread-local storage.
288fn build_tls_cache_body(full_cache_type: &syn::Type, cache_new: &syn::Expr,
289 cloned_args: &syn::ExprTuple, inner_fn_call: &syn::ExprCall) -> syn::Block
290{
291 parse_quote! {
292 {
293 use std::cell::UnsafeCell;
294 use std::thread_local;
295 thread_local!(
296 // We use `UnsafeCell` here to allow recursion. Since it is in the TLS, it should
297 // not introduce any actual unsafety.
298 static cache: UnsafeCell<#full_cache_type> =
299 UnsafeCell::new(#cache_new);
300 );
301 cache.with(|c| {
302 let mut cache_ref = unsafe { &mut *c.get() };
303 let cloned_args = #cloned_args;
304
305 let stored_result = cache_ref.get_mut(&cloned_args);
306 if let Some(stored_result) = stored_result {
307 stored_result.clone()
308 } else {
309 let ret = #inner_fn_call;
310 cache_ref.insert(cloned_args, ret.clone());
311 ret
312 }
313 })
314 }
315 }
316}
317
318// Build the body of the caching function which guards the static cache with a mutex.
319fn build_mutex_cache_body(full_cache_type: &syn::Type, cache_new: &syn::Expr,
320 cloned_args: &syn::ExprTuple, inner_fn_call: &syn::ExprCall) -> syn::Block
321{
322 parse_quote! {
323 {
324 use lazy_static::lazy_static;
325 use std::sync::Mutex;
326
327 lazy_static! {
328 static ref cache: Mutex<#full_cache_type> =
329 Mutex::new(#cache_new);
330 }
331
332 let cloned_args = #cloned_args;
333
334 let mut cache_unlocked = cache.lock().unwrap();
335 let stored_result = cache_unlocked.get_mut(&cloned_args);
336 if let Some(stored_result) = stored_result {
337 return stored_result.clone();
338 };
339
340 // must unlock here to allow potentially recursive call
341 drop(cache_unlocked);
342
343 let ret = #inner_fn_call;
344 let mut cache_unlocked = cache.lock().unwrap();
345 cache_unlocked.insert(cloned_args, ret.clone());
346 ret
347 }
348 }
349}
350
351fn get_cache_fn_return_type(original_fn: &syn::ItemFn) -> Result<Box<syn::Type>> {
352 if let syn::ReturnType::Type(_, ref ty) = original_fn.decl.output {
353 Ok(ty.clone())
354 } else {
355 let diag = original_fn.ident.span().unstable()
356 .error("There's no point of caching the output of a function that has no output");
357 return Err(DiagnosticError::new(diag));
358 }
359}
360
361fn path_from_ident(ident: syn::Ident) -> syn::Expr {
362 let mut segments: Punctuated<_, Token![::]> = Punctuated::new();
363 segments.push(syn::PathSegment { ident: ident, arguments: syn::PathArguments::None });
364 syn::Expr::Path(syn::ExprPath { attrs: Vec::new(), qself: None, path: syn::Path { leading_colon: None, segments: segments} })
365}
366
367fn get_lru_size(attr: TokenStream) -> Result<usize> {
368 let value: result::Result<syn::LitInt, _> = syn::parse(attr.clone());
369
370 if let Ok(val) = value {
371 Ok(val.value() as usize)
372 } else {
373 let diag = proc_macro2::Span::call_site().unstable()
374 .error("The lru_cache macro must specify a maximum cache size as an argument");
375 Err(DiagnosticError::new_with_syn_error(diag, value.err().unwrap()))
376 }
377}
378
379fn make_cloned_args_tuple(args: &Punctuated<syn::Expr, Token![,]>) -> syn::ExprTuple {
380 let mut cloned_args = Punctuated::<_, Token![,]>::new();
381 for arg in args {
382 let call = syn::ExprMethodCall {
383 attrs: Vec::new(),
384 receiver: Box::new(arg.clone()),
385 dot_token: syn::token::Dot { spans: [arg.span(); 1] },
386 method: syn::Ident::new("clone", proc_macro2::Span::call_site()),
387 turbofish: None,
388 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
389 args: Punctuated::new(),
390 };
391 cloned_args.push(syn::Expr::MethodCall(call));
392 }
393 syn::ExprTuple {
394 attrs: Vec::new(),
395 paren_token: syn::token::Paren { span: proc_macro2::Span::call_site() },
396 elems: cloned_args,
397 }
398}
399
400fn get_args_and_types(f: &syn::ItemFn, config: &config::Config) ->
401 Result<(Punctuated<syn::Expr, Token![,]>, Punctuated<syn::Type, Token![,]>, Punctuated<syn::Expr, Token![,]>)>
402{
403 let mut call_args = Punctuated::<_, Token![,]>::new();
404 let mut types = Punctuated::<_, Token![,]>::new();
405 let mut cache_args = Punctuated::<_, Token![,]>::new();
406
407 for input in &f.decl.inputs {
408 match input {
409 syn::FnArg::SelfValue(p) => {
410 let diag = p.span().unstable()
411 .error("`self` arguments are currently unsupported by lru_cache");
412 return Err(DiagnosticError::new(diag));
413 }
414 syn::FnArg::SelfRef(p) => {
415 let diag = p.span().unstable()
416 .error("`&self` arguments are currently unsupported by lru_cache");
417 return Err(DiagnosticError::new(diag));
418 }
419 syn::FnArg::Captured(arg_captured) => {
420 let mut segments: syn::punctuated::Punctuated<_, Token![::]> = syn::punctuated::Punctuated::new();
421 let arg_name;
422 if let syn::Pat::Ident(ref pat_ident) = arg_captured.pat {
423 arg_name = pat_ident.ident.clone();
424 if let Some(m) = pat_ident.mutability {
425 if !config.ignore_args.contains(&arg_name) {
426 let diag = m.span.unstable()
427 .error("`mut` arguments are not supported with lru_cache as this could lead to incorrect results being stored");
428 return Err(DiagnosticError::new(diag));
429 }
430 }
431 segments.push(syn::PathSegment { ident: pat_ident.ident.clone(), arguments: syn::PathArguments::None });
432 } else {
433 let diag = arg_captured.span().unstable()
434 .error("unsupported argument kind");
435 return Err(DiagnosticError::new(diag));
436 }
437
438 let arg_path = syn::Expr::Path(syn::ExprPath { attrs: Vec::new(), qself: None, path: syn::Path { leading_colon: None, segments } });
439
440 if !config.ignore_args.contains(&arg_name) {
441
442 // If the arg type is a reference, remove the reference because the arg will be cloned
443 if let syn::Type::Reference(type_reference) = &arg_captured.ty {
444 types.push(type_reference.elem.as_ref().to_owned()); // as_ref -> to_owned unboxes the type
445 } else {
446 types.push(arg_captured.ty.clone());
447 }
448
449 cache_args.push(arg_path.clone());
450 }
451
452
453 call_args.push(arg_path);
454 },
455 syn::FnArg::Inferred(p) => {
456 let diag = p.span().unstable()
457 .error("inferred arguments are currently unsupported by lru_cache");
458 return Err(DiagnosticError::new(diag));
459 }
460 syn::FnArg::Ignored(p) => {
461 let diag = p.span().unstable()
462 .error("ignored arguments are currently unsupported by lru_cache");
463 return Err(DiagnosticError::new(diag));
464 }
465 }
466 }
467
468 if types.len() == 1 {
469 types.push_punct(syn::token::Comma { spans: [proc_macro2::Span::call_site(); 1] })
470 }
471
472 Ok((call_args, types, cache_args))
473}