Repository: jaemk/cached (language: rust)

Issue:
macro: `pub fn` is not supported
The macro doesn't let me assign privacy to the function.

```
error: no rules expected the token `pub`
  --> src/...
   |
24 |         pub fn get(a_keyword: &str) -> Option<Self> = {
   |         ^^^
```


Discussion / hints:
Woops... that's an oversight!
The [Rust API Guidelines](https://rust-lang-nursery.github.io/api-guidelines/macros.html) calls this [`C-MACRO-VIS`](https://rust-lang-nursery.github.io/api-guidelines/macros.html#item-macros-support-visibility-specifiers-c-macro-vis).

Another point from that checklist that applies here is [`C-MACRO-ATTR`](https://rust-lang-nursery.github.io/api-guidelines/macros.html#item-macros-compose-well-with-attributes-c-macro-attr)

To fix both, use something like this:

```rust
macro_rules! fn_macro {
    // Allow `fn foo()`
    ($(#[$attr:meta])* fn $name:ident($arg:ident: $ty:ty) $fun:block) => {
        fn_macro!{
            internal $(#[$attr])* [] fn $name($arg: $ty) $fun
        }
    };
    // Allow `pub(...) fn foo()`
    ($(#[$attr:meta])* pub($($vis:tt)+) fn $name:ident($arg:ident: $ty:ty) $fun:block) => {
        fn_macro!{
            internal $(#[$attr])* [pub($($vis)+)] fn $name($arg: $ty) $fun
        }
    };
    // Allow `pub fn foo()`
    ($(#[$attr:meta])* pub fn $name:ident($arg:ident: $ty:ty) $fun:block) => {
        fn_macro!{
            internal $(#[$attr])* [pub] fn $name($arg: $ty) $fun
        }
    };
    (internal $(#[$attr:meta])* [$($vis:tt)*] fn $name:ident($arg:ident: $ty:ty) $fun:block) => {
        $(#[$attr])*
        $($vis)* fn $name($arg: $ty) $fun
    };
}

fn_macro!{
    #[cfg(debug_assertions)]
    pub(crate) fn foo(bar: i32) {
        println!("{}", bar);
    }
}

fn main() {
    foo(42);
}
```
Any chance to have it working out of the box? I also stuck with that issue, cannot use cached. The only solution I see now is to create mirroring function which will be public and will call to internal cached.
But there is another issue which prevents me to do that: 
```
error: no rules expected the token `&`
  --> locale_loader/mod.rs:73:29
   |
73 |         fn translate_cached(&'a self, locale: &str, key: &str) -> String {
```

I am trying to cache the structure method
To cache a structure method you have to use a workaround like [this](https://github.com/jaemk/cached/issues/16#issuecomment-431941432).

The reason is that the `cached` macros expand to two items instead of just one which prevents them being used in an impl block.

Fix patch:
diff --git a/.gitignore b/.gitignore
index a9d37c5..6a818a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 target
 Cargo.lock
+
+.idea/
diff --git a/Cargo.toml b/Cargo.toml
index f68c5ed..0ea8e36 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,5 +14,13 @@ edition = "2018"
 [badges]
 travis-ci = { repository = "jaemk/cached", branch = "master" }
 
+[features]
+default = ["proc_macro"]
+proc_macro = []
+
 [dependencies]
 once_cell = "1"
+cached_proc_macro = { path = "cached_proc_macro"}
+
+[workspace]
+members = ["cached_proc_macro"]
\ No newline at end of file
diff --git a/cached_proc_macro/Cargo.toml b/cached_proc_macro/Cargo.toml
new file mode 100644
index 0000000..e4f6add
--- /dev/null
+++ b/cached_proc_macro/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "cached_proc_macro"
+version = "0.1.0"
+authors = ["csos95 <csoscss@gmail.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "1.0.6"
+darling = "0.10.2"
+
+[dependencies.syn]
+version = "1.0.27"
+features = ["full"]
\ No newline at end of file
diff --git a/cached_proc_macro/src/lib.rs b/cached_proc_macro/src/lib.rs
new file mode 100644
index 0000000..bb52514
--- /dev/null
+++ b/cached_proc_macro/src/lib.rs
@@ -0,0 +1,210 @@
+//! # Attributes
+//! - **Cache Name:** Use `name = "CACHE_NAME"` to specify the name for the generated cache.
+//! - **Cache Type:** The default cache type is `UnboundCache`.
+//! You specify which of the built-in cache types to use with `unbound`, `size = cache_size`, or `time = lifetime_in_seconds`
+//! - **Cache Create:** You can specify the cache creation with `create = "{ CacheType::new() }"`.
+//! - **Custom Cache Type:** You can use `type = "CacheType"` to specify the type of cache to use.
+//! This requires create to also be set.
+//! - **Cache Key:** Use `key = "KeyType"` to specify what type to use for the cache key.
+//! This requires convert to also be set.
+//! - **Cache Key Convert:** Use `convert = "{ convert_inputs_to_key }"`.
+//! This requires either key or type to also be set.
+//! - **Caching Result/Option:** If your function returns a `Result` or `Option`
+//! you may want to use `result` or `option` to only cache when the output is `Ok` or `Some`
+//! ## Note
+//! The `type`, `create`, `key`, and `convert` attributes must be in a `String`
+//! This is because darling, which is used for parsing the attributes, does not support parsing attributes into `Type` or `Block`.
+
+use darling::FromMeta;
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{
+    parse_macro_input, parse_str, AttributeArgs, Block, FnArg, Ident, ItemFn, Pat, ReturnType, Type,
+};
+
+#[derive(FromMeta)]
+struct MacroArgs {
+    #[darling(default)]
+    name: Option<String>,
+    #[darling(default)]
+    unbound: bool,
+    #[darling(default)]
+    size: Option<usize>,
+    #[darling(default)]
+    time: Option<u64>,
+    #[darling(default)]
+    key: Option<String>,
+    #[darling(default)]
+    convert: Option<String>,
+    #[darling(default)]
+    result: bool,
+    #[darling(default)]
+    option: bool,
+    #[darling(default, rename = "type")]
+    cache_type: Option<String>,
+    #[darling(default, rename = "create")]
+    cache_create: Option<String>,
+}
+
+#[proc_macro_attribute]
+pub fn cached(args: TokenStream, input: TokenStream) -> TokenStream {
+    let attr_args = parse_macro_input!(args as AttributeArgs);
+    let args = match MacroArgs::from_list(&attr_args) {
+        Ok(v) => v,
+        Err(e) => {
+            return TokenStream::from(e.write_errors());
+        }
+    };
+    let input = parse_macro_input!(input as ItemFn);
+
+    // pull out the parts of the input
+    let _attributes = input.attrs;
+    let visibility = input.vis;
+    let signature = input.sig;
+    let body = input.block;
+
+    // pull out the parts of the function signature
+    let fn_ident = signature.ident.clone();
+    let inputs = signature.inputs.clone();
+    let output = signature.output.clone();
+
+    // pull out the names and types of the function inputs
+    let input_tys = inputs
+        .iter()
+        .map(|input| match input {
+            FnArg::Receiver(_) => panic!("methods (functions taking 'self') are not supported"),
+            FnArg::Typed(pat_type) => pat_type.ty.clone(),
+        })
+        .collect::<Vec<Box<Type>>>();
+
+    let input_names = inputs
+        .iter()
+        .map(|input| match input {
+            FnArg::Receiver(_) => panic!("methods (functions taking 'self') are not supported"),
+            FnArg::Typed(pat_type) => pat_type.pat.clone(),
+        })
+        .collect::<Vec<Box<Pat>>>();
+
+    // pull out the output type
+    let output_ty = match &output {
+        ReturnType::Default => quote! {()},
+        ReturnType::Type(_, ty) => quote! {#ty},
+    };
+
+    // make the cache identifier
+    let cache_ident = match args.name {
+        Some(name) => Ident::new(&name, fn_ident.span()),
+        None => Ident::new(&fn_ident.to_string().to_uppercase(), fn_ident.span()),
+    };
+
+    // make the cache key type and block that converts the inputs into the key type
+    let (cache_key_ty, key_convert_block) = match (&args.key, &args.convert, &args.cache_type) {
+        (Some(key_str), Some(convert_str), _) => {
+            let cache_key_ty = parse_str::<Type>(key_str).expect("unable to parse cache key type");
+
+            let key_convert_block =
+                parse_str::<Block>(convert_str).expect("unable to parse key convert block");
+
+            (quote! {#cache_key_ty}, quote! {#key_convert_block})
+        }
+        (None, Some(convert_str), Some(_)) => {
+            let key_convert_block =
+                parse_str::<Block>(convert_str).expect("unable to parse key convert block");
+
+            (quote! {}, quote! {#key_convert_block})
+        }
+        (None, None, _) => (
+            quote! {(#(#input_tys),*)},
+            quote! {(#(#input_names.clone()),*)},
+        ),
+        (Some(_), None, _) => panic!("key requires convert to be set"),
+        (None, Some(_), None) => panic!("convert requires key or type to be set"),
+    };
+
+    // make the cache type and create statement
+    let (cache_ty, cache_create) = match (
+        &args.unbound,
+        &args.size,
+        &args.time,
+        &args.cache_type,
+        &args.cache_create,
+    ) {
+        (true, None, None, None, None) => {
+            let cache_ty = quote! {cached::UnboundCache<#cache_key_ty, #output_ty>};
+            let cache_create = quote! {cached::UnboundCache::new()};
+            (cache_ty, cache_create)
+        }
+        (false, Some(size), None, None, None) => {
+            let cache_ty = quote! {cached::SizedCache<#cache_key_ty, #output_ty>};
+            let cache_create = quote! {cached::SizedCache::with_size(#size)};
+            (cache_ty, cache_create)
+        }
+        (false, None, Some(time), None, None) => {
+            let cache_ty = quote! {cached::TimedCache<#cache_key_ty, #output_ty>};
+            let cache_create = quote! {cached::TimedCache::with_lifespan(#time)};
+            (cache_ty, cache_create)
+        }
+        (false, None, None, None, None) => {
+            let cache_ty = quote! {cached::UnboundCache<#cache_key_ty, #output_ty>};
+            let cache_create = quote! {cached::UnboundCache::new()};
+            (cache_ty, cache_create)
+        }
+        (false, None, None, Some(type_str), Some(create_str)) => {
+            let cache_type = parse_str::<Type>(type_str).expect("unable to parse cache type");
+
+            let cache_create =
+                parse_str::<Block>(create_str).expect("unable to parse cache create block");
+
+            (quote! { #cache_type }, quote! { #cache_create })
+        }
+        (false, None, None, Some(_), None) => panic!("type requires create to also be set"),
+        (false, None, None, None, Some(_)) => panic!("create requires type to also be set"),
+        _ => panic!("cache types (unbound, size, time, or type and create) are mutually exclusive"),
+    };
+
+    // make the set cache block
+    let set_cache_block = match (&args.result, &args.option) {
+        (false, false) => quote! { cache.cache_set(key, result.clone()); },
+        (true, false) => quote! {
+            match result.clone() {
+                Ok(result) => cache.cache_set(key, Ok(result)),
+                _ => {},
+            }
+        },
+        (false, true) => quote! {
+            match result.clone() {
+                Some(result) => cache.cache_set(key, Some(result)),
+                _ => {},
+            }
+        },
+        _ => panic!("the result and option attributes are mutually exclusive"),
+    };
+
+    // put it all together
+    let expanded = quote! {
+        #visibility static #cache_ident: ::cached::once_cell::sync::Lazy<std::sync::Mutex<#cache_ty>> = ::cached::once_cell::sync::Lazy::new(|| std::sync::Mutex::new(#cache_create));
+        #visibility #signature {
+            use cached::Cached;
+            let key = #key_convert_block;
+            {
+                // check if the result is cached
+                let mut cache = #cache_ident.lock().unwrap();
+                if let Some(result) = cache.cache_get(&key) {
+                    return result.clone();
+                }
+            }
+
+            // run the function and cache the result
+            fn inner(#inputs) #output #body;
+            let result = inner(#(#input_names),*);
+
+            let mut cache = #cache_ident.lock().unwrap();
+            // cache.cache_set(key, result.clone());
+            #set_cache_block
+
+            result
+        }
+    };
+
+    expanded.into()
+}
diff --git a/examples/basic_proc_macro.rs b/examples/basic_proc_macro.rs
new file mode 100644
index 0000000..1a8fd8d
--- /dev/null
+++ b/examples/basic_proc_macro.rs
@@ -0,0 +1,35 @@
+use cached::proc_macro::cached;
+use std::thread::sleep;
+use std::time::{Duration, Instant};
+
+#[cached(size = 50)]
+fn slow_fn(n: u32) -> String {
+    if n == 0 {
+        return "done".to_string();
+    }
+    sleep(Duration::new(1, 0));
+    slow_fn(n - 1)
+}
+
+pub fn main() {
+    println!("Initial run...");
+    let now = Instant::now();
+    let _ = slow_fn(10);
+    println!("Elapsed: {}\n", now.elapsed().as_secs());
+
+    println!("Cached run...");
+    let now = Instant::now();
+    let _ = slow_fn(10);
+    println!("Elapsed: {}\n", now.elapsed().as_secs());
+
+    // Inspect the cache
+    {
+        use cached::Cached; // must be in scope to access cache
+
+        println!(" ** Cache info **");
+        let cache = SLOW_FN.lock().unwrap();
+        println!("hits=1 -> {:?}", cache.cache_hits().unwrap() == 1);
+        println!("misses=11 -> {:?}", cache.cache_misses().unwrap() == 11);
+        // make sure the cache-lock is dropped
+    }
+}
diff --git a/examples/kitchen_sink_proc_macro.rs b/examples/kitchen_sink_proc_macro.rs
new file mode 100644
index 0000000..d2291e3
--- /dev/null
+++ b/examples/kitchen_sink_proc_macro.rs
@@ -0,0 +1,149 @@
+use std::cmp::Eq;
+use std::collections::HashMap;
+use std::hash::Hash;
+
+use std::thread::sleep;
+use std::time::Duration;
+
+use cached::proc_macro::cached;
+use cached::{Cached, SizedCache, UnboundCache};
+
+// cached shorthand, uses the default unbounded cache.
+// Equivalent to specifying `type = "UnboundCache<(u32), u32>", create= "{ UnboundCache::new() }"`
+#[cached]
+fn fib(n: u32) -> u32 {
+    if n == 0 || n == 1 {
+        return n;
+    }
+    fib(n - 1) + fib(n - 2)
+}
+
+// Same as above, but preallocates some space.
+#[cached(
+    type = "UnboundCache<u32, u32>",
+    create = "{ UnboundCache::with_capacity(50) }"
+)]
+fn fib_specific(n: u32) -> u32 {
+    if n == 0 || n == 1 {
+        return n;
+    }
+    fib_specific(n - 1) + fib_specific(n - 2)
+}
+
+// Specify a specific cache type
+// Note that the cache key type is a tuple of function argument types.
+#[cached(
+    type = "SizedCache<(u32, u32), u32>",
+    create = "{ SizedCache::with_size(100) }"
+)]
+fn slow(a: u32, b: u32) -> u32 {
+    sleep(Duration::new(2, 0));
+    return a * b;
+}
+
+// Specify a specific cache type and an explicit key expression
+// Note that the cache key type is a `String` created from the borrow arguments
+// Note that key is not used, convert requires either key or type to be set.
+#[cached(
+    type = "SizedCache<String, usize>",
+    create = "{ SizedCache::with_size(100) }",
+    convert = r#"{ format!("{}{}", a, b) }"#
+)]
+fn keyed(a: &str, b: &str) -> usize {
+    let size = a.len() + b.len();
+    sleep(Duration::new(size as u64, 0));
+    size
+}
+
+// Implement our own cache type
+struct MyCache<K: Hash + Eq, V> {
+    store: HashMap<K, V>,
+    capacity: usize,
+}
+impl<K: Hash + Eq, V> MyCache<K, V> {
+    pub fn with_capacity(size: usize) -> MyCache<K, V> {
+        MyCache {
+            store: HashMap::with_capacity(size),
+            capacity: size,
+        }
+    }
+}
+impl<K: Hash + Eq, V> Cached<K, V> for MyCache<K, V> {
+    fn cache_get(&mut self, k: &K) -> Option<&V> {
+        self.store.get(k)
+    }
+    fn cache_get_mut(&mut self, k: &K) -> Option<&mut V> {
+        self.store.get_mut(k)
+    }
+    fn cache_set(&mut self, k: K, v: V) {
+        self.store.insert(k, v);
+    }
+    fn cache_remove(&mut self, k: &K) -> Option<V> {
+        self.store.remove(k)
+    }
+    fn cache_clear(&mut self) {
+        self.store.clear();
+    }
+    fn cache_reset(&mut self) {
+        self.store = HashMap::with_capacity(self.capacity);
+    }
+    fn cache_size(&self) -> usize {
+        self.store.len()
+    }
+}
+
+// Specify our custom cache and supply an instance to use
+#[cached(type = "MyCache<u32, ()>", create = "{ MyCache::with_capacity(50) }")]
+fn custom(n: u32) -> () {
+    if n == 0 {
+        return;
+    }
+    custom(n - 1)
+}
+
+pub fn main() {
+    println!("\n ** default cache **");
+    fib(3);
+    fib(3);
+    {
+        let cache = FIB.lock().unwrap();
+        println!("hits: {:?}", cache.cache_hits());
+        println!("misses: {:?}", cache.cache_misses());
+        // make sure lock is dropped
+    }
+    fib(10);
+    fib(10);
+
+    println!("\n ** specific cache **");
+    fib_specific(20);
+    fib_specific(20);
+    {
+        let cache = FIB_SPECIFIC.lock().unwrap();
+        println!("hits: {:?}", cache.cache_hits());
+        println!("misses: {:?}", cache.cache_misses());
+        // make sure lock is dropped
+    }
+    fib_specific(20);
+    fib_specific(20);
+
+    println!("\n ** custom cache **");
+    custom(25);
+    {
+        let cache = CUSTOM.lock().unwrap();
+        println!("hits: {:?}", cache.cache_hits());
+        println!("misses: {:?}", cache.cache_misses());
+        // make sure lock is dropped
+    }
+
+    println!("\n ** slow func **");
+    println!(" - first run `slow(10)`");
+    slow(10, 10);
+    println!(" - second run `slow(10)`");
+    slow(10, 10);
+    {
+        let cache = SLOW.lock().unwrap();
+        println!("hits: {:?}", cache.cache_hits());
+        println!("misses: {:?}", cache.cache_misses());
+        // make sure the cache-lock is dropped
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index f0b4075..84dd37a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -238,6 +238,11 @@ pub mod stores;
 
 pub use stores::{SizedCache, TimedCache, UnboundCache};
 
+#[cfg(feature = "proc_macro")]
+pub mod proc_macro {
+    pub use cached_proc_macro::cached;
+}
+
 /// Cache operations
 pub trait Cached<K, V> {
     /// Attempt to retrieve a cached value


Test patch: