Skip to content

Commit 05d7493

Browse files
committed
Separation of proc macros, nightly clippy and some tiny changes
1 parent dd61692 commit 05d7493

20 files changed

+1229
-1125
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44
## Added
55
## Changed
6+
- Better code generation for `#[cached]` when the `sync_writes` flag is true.
67
## Removed
78

89
## [0.41.0]

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ of un-cached arguments, specify `#[cached(sync_writes = true)]` / `#[once(sync_w
3535

3636
The procedural macros (`#[cached]`, `#[once]`, `#[io_cached]`) offer more features, including async support.
3737
See the [`proc_macro`](crate::proc_macro) and [`macros`](crate::macros) modules for more samples, and the
38-
[`examples`](https://github.com/jaemk/cached/tree/master/examples) directory for runnable snippets.`
38+
[`examples`](https://github.com/jaemk/cached/tree/master/examples) directory for runnable snippets.
3939

4040
Any custom cache that implements `cached::Cached`/`cached::CachedAsync` can be used with the `#[cached]`/`#[once]`/`cached!` macros in place of the built-ins.
4141
Any custom cache that implements `cached::IOCached`/`cached::IOCachedAsync` can be used with the `#[io_cached]` macro.

cached_proc_macro/src/cached.rs

+322
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
use crate::helpers::*;
2+
use darling::FromMeta;
3+
use proc_macro::TokenStream;
4+
use quote::quote;
5+
use syn::spanned::Spanned;
6+
use syn::{parse_macro_input, parse_str, AttributeArgs, Block, Ident, ItemFn, ReturnType, Type};
7+
8+
#[derive(FromMeta)]
9+
struct MacroArgs {
10+
#[darling(default)]
11+
name: Option<String>,
12+
#[darling(default)]
13+
unbound: bool,
14+
#[darling(default)]
15+
size: Option<usize>,
16+
#[darling(default)]
17+
time: Option<u64>,
18+
#[darling(default)]
19+
time_refresh: bool,
20+
#[darling(default)]
21+
key: Option<String>,
22+
#[darling(default)]
23+
convert: Option<String>,
24+
#[darling(default)]
25+
result: bool,
26+
#[darling(default)]
27+
option: bool,
28+
#[darling(default)]
29+
sync_writes: bool,
30+
#[darling(default)]
31+
with_cached_flag: bool,
32+
#[darling(default, rename = "type")]
33+
cache_type: Option<String>,
34+
#[darling(default, rename = "create")]
35+
cache_create: Option<String>,
36+
}
37+
38+
/// # Attributes
39+
/// - `name`: (optional, string) specify the name for the generated cache, defaults to the function name uppercase.
40+
/// - `size`: (optional, usize) specify an LRU max size, implies the cache type is a `SizedCache` or `TimedSizedCache`.
41+
/// - `time`: (optional, u64) specify a cache TTL in seconds, implies the cache type is a `TimedCache` or `TimedSizedCache`.
42+
/// - `time_refresh`: (optional, bool) specify whether to refresh the TTL on cache hits.
43+
/// - `sync_writes`: (optional, bool) specify whether to synchronize the execution of writing of uncached values.
44+
/// - `type`: (optional, string type) The cache store type to use. Defaults to `UnboundCache`. When `unbound` is
45+
/// specified, defaults to `UnboundCache`. When `size` is specified, defaults to `SizedCache`.
46+
/// When `time` is specified, defaults to `TimedCached`.
47+
/// When `size` and `time` are specified, defaults to `TimedSizedCache`. When `type` is
48+
/// specified, `create` must also be specified.
49+
/// - `create`: (optional, string expr) specify an expression used to create a new cache store, e.g. `create = r##"{ CacheType::new() }"##`.
50+
/// - `key`: (optional, string type) specify what type to use for the cache key, e.g. `key = "u32"`.
51+
/// When `key` is specified, `convert` must also be specified.
52+
/// - `convert`: (optional, string expr) specify an expression used to convert function arguments to a cache
53+
/// key, e.g. `convert = r##"{ format!("{}:{}", arg1, arg2) }"##`. When `convert` is specified,
54+
/// `key` or `type` must also be set.
55+
/// - `result`: (optional, bool) If your function returns a `Result`, only cache `Ok` values returned by the function.
56+
/// - `option`: (optional, bool) If your function returns an `Option`, only cache `Some` values returned by the function.
57+
/// - `with_cached_flag`: (optional, bool) If your function returns a `cached::Return` or `Result<cached::Return, E>`,
58+
/// the `cached::Return.was_cached` flag will be updated when a cached value is returned.
59+
///
60+
/// ## Note
61+
/// The `type`, `create`, `key`, and `convert` attributes must be in a `String`
62+
/// This is because darling, which is used for parsing the attributes, does not support directly parsing
63+
/// attributes into `Type`s or `Block`s.
64+
pub fn cached(args: TokenStream, input: TokenStream) -> TokenStream {
65+
let attr_args = parse_macro_input!(args as AttributeArgs);
66+
let args = match MacroArgs::from_list(&attr_args) {
67+
Ok(v) => v,
68+
Err(e) => {
69+
return TokenStream::from(e.write_errors());
70+
}
71+
};
72+
let input = parse_macro_input!(input as ItemFn);
73+
74+
// pull out the parts of the input
75+
let mut attributes = input.attrs;
76+
let visibility = input.vis;
77+
let signature = input.sig;
78+
let body = input.block;
79+
80+
// pull out the parts of the function signature
81+
let fn_ident = signature.ident.clone();
82+
let inputs = signature.inputs.clone();
83+
let output = signature.output.clone();
84+
let asyncness = signature.asyncness;
85+
86+
let input_tys = get_input_types(&inputs);
87+
let input_names = get_input_names(&inputs);
88+
89+
// pull out the output type
90+
let output_ty = match &output {
91+
ReturnType::Default => quote! {()},
92+
ReturnType::Type(_, ty) => quote! {#ty},
93+
};
94+
95+
let output_span = output_ty.span();
96+
let output_ts = TokenStream::from(output_ty.clone());
97+
let output_parts = get_output_parts(&output_ts);
98+
let output_string = output_parts.join("::");
99+
let output_type_display = output_ts.to_string().replace(' ', "");
100+
101+
if check_with_cache_flag(args.with_cached_flag, output_string) {
102+
return with_cache_flag_error(output_span, output_type_display);
103+
}
104+
105+
let cache_value_ty = find_value_type(args.result, args.option, &output, output_ty);
106+
107+
// make the cache identifier
108+
let cache_ident = match args.name {
109+
Some(ref name) => Ident::new(name, fn_ident.span()),
110+
None => Ident::new(&fn_ident.to_string().to_uppercase(), fn_ident.span()),
111+
};
112+
113+
let (cache_key_ty, key_convert_block) = make_cache_key_type(
114+
&args.key,
115+
&args.convert,
116+
&args.cache_type,
117+
input_tys,
118+
&input_names,
119+
);
120+
121+
// make the cache type and create statement
122+
let (cache_ty, cache_create) = match (
123+
&args.unbound,
124+
&args.size,
125+
&args.time,
126+
&args.cache_type,
127+
&args.cache_create,
128+
&args.time_refresh,
129+
) {
130+
(true, None, None, None, None, _) => {
131+
let cache_ty = quote! {cached::UnboundCache<#cache_key_ty, #cache_value_ty>};
132+
let cache_create = quote! {cached::UnboundCache::new()};
133+
(cache_ty, cache_create)
134+
}
135+
(false, Some(size), None, None, None, _) => {
136+
let cache_ty = quote! {cached::SizedCache<#cache_key_ty, #cache_value_ty>};
137+
let cache_create = quote! {cached::SizedCache::with_size(#size)};
138+
(cache_ty, cache_create)
139+
}
140+
(false, None, Some(time), None, None, time_refresh) => {
141+
let cache_ty = quote! {cached::TimedCache<#cache_key_ty, #cache_value_ty>};
142+
let cache_create =
143+
quote! {cached::TimedCache::with_lifespan_and_refresh(#time, #time_refresh)};
144+
(cache_ty, cache_create)
145+
}
146+
(false, Some(size), Some(time), None, None, time_refresh) => {
147+
let cache_ty = quote! {cached::TimedSizedCache<#cache_key_ty, #cache_value_ty>};
148+
let cache_create = quote! {cached::TimedSizedCache::with_size_and_lifespan_and_refresh(#size, #time, #time_refresh)};
149+
(cache_ty, cache_create)
150+
}
151+
(false, None, None, None, None, _) => {
152+
let cache_ty = quote! {cached::UnboundCache<#cache_key_ty, #cache_value_ty>};
153+
let cache_create = quote! {cached::UnboundCache::new()};
154+
(cache_ty, cache_create)
155+
}
156+
(false, None, None, Some(type_str), Some(create_str), _) => {
157+
let cache_type = parse_str::<Type>(type_str).expect("unable to parse cache type");
158+
159+
let cache_create =
160+
parse_str::<Block>(create_str).expect("unable to parse cache create block");
161+
162+
(quote! { #cache_type }, quote! { #cache_create })
163+
}
164+
(false, None, None, Some(_), None, _) => {
165+
panic!("type requires create to also be set")
166+
}
167+
(false, None, None, None, Some(_), _) => {
168+
panic!("create requires type to also be set")
169+
}
170+
_ => panic!(
171+
"cache types (unbound, size and/or time, or type and create) are mutually exclusive"
172+
),
173+
};
174+
175+
// make the set cache and return cache blocks
176+
let (set_cache_block, return_cache_block) = match (&args.result, &args.option) {
177+
(false, false) => {
178+
let set_cache_block = quote! { cache.cache_set(key, result.clone()); };
179+
let return_cache_block = if args.with_cached_flag {
180+
quote! { let mut r = result.clone(); r.was_cached = true; return r }
181+
} else {
182+
quote! { return result.clone() }
183+
};
184+
(set_cache_block, return_cache_block)
185+
}
186+
(true, false) => {
187+
let set_cache_block = quote! {
188+
if let Ok(result) = &result {
189+
cache.cache_set(key, result.clone());
190+
}
191+
};
192+
let return_cache_block = if args.with_cached_flag {
193+
quote! { let mut r = result.clone(); r.was_cached = true; return Ok(r) }
194+
} else {
195+
quote! { return Ok(result.clone()) }
196+
};
197+
(set_cache_block, return_cache_block)
198+
}
199+
(false, true) => {
200+
let set_cache_block = quote! {
201+
if let Some(result) = &result {
202+
cache.cache_set(key, result.clone());
203+
}
204+
};
205+
let return_cache_block = if args.with_cached_flag {
206+
quote! { let mut r = result.clone(); r.was_cached = true; return Some(r) }
207+
} else {
208+
quote! { return Some(result.clone()) }
209+
};
210+
(set_cache_block, return_cache_block)
211+
}
212+
_ => panic!("the result and option attributes are mutually exclusive"),
213+
};
214+
215+
let set_cache_and_return = quote! {
216+
#set_cache_block
217+
result
218+
};
219+
let lock;
220+
let function_call;
221+
let cache_type;
222+
if asyncness.is_some() {
223+
lock = quote! {
224+
// try to get a lock first
225+
let mut cache = #cache_ident.lock().await;
226+
};
227+
228+
function_call = quote! {
229+
// run the function and cache the result
230+
async fn inner(#inputs) #output #body;
231+
let result = inner(#(#input_names),*).await;
232+
};
233+
234+
cache_type = quote! {
235+
#visibility static #cache_ident: ::cached::once_cell::sync::Lazy<::cached::async_sync::Mutex<#cache_ty>> = ::cached::once_cell::sync::Lazy::new(|| ::cached::async_sync::Mutex::new(#cache_create));
236+
};
237+
} else {
238+
lock = quote! {
239+
// try to get a lock first
240+
let mut cache = #cache_ident.lock().unwrap();
241+
};
242+
243+
function_call = quote! {
244+
// run the function and cache the result
245+
fn inner(#inputs) #output #body;
246+
let result = inner(#(#input_names),*);
247+
};
248+
249+
cache_type = quote! {
250+
#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));
251+
};
252+
}
253+
254+
let prime_do_set_return_block = quote! {
255+
#lock
256+
#function_call
257+
#set_cache_and_return
258+
};
259+
260+
let do_set_return_block = if args.sync_writes {
261+
quote! {
262+
#lock
263+
if let Some(result) = cache.cache_get(&key) {
264+
#return_cache_block
265+
}
266+
#function_call
267+
#set_cache_and_return
268+
}
269+
} else {
270+
quote! {
271+
{
272+
#lock
273+
if let Some(result) = cache.cache_get(&key) {
274+
#return_cache_block
275+
}
276+
}
277+
#function_call
278+
#lock
279+
#set_cache_and_return
280+
}
281+
};
282+
283+
let signature_no_muts = get_mut_signature(signature);
284+
285+
// create a signature for the cache-priming function
286+
let prime_fn_ident = Ident::new(&format!("{}_prime_cache", &fn_ident), fn_ident.span());
287+
let mut prime_sig = signature_no_muts.clone();
288+
prime_sig.ident = prime_fn_ident;
289+
290+
// make cached static, cached function and prime cached function doc comments
291+
let cache_ident_doc = format!("Cached static for the [`{}`] function.", fn_ident);
292+
let prime_fn_indent_doc = format!("Primes the cached function [`{}`].", fn_ident);
293+
let cache_fn_doc_extra = format!(
294+
"This is a cached function that uses the [`{}`] cached static.",
295+
cache_ident
296+
);
297+
fill_in_attributes(&mut attributes, cache_fn_doc_extra);
298+
299+
// put it all together
300+
let expanded = quote! {
301+
// Cached static
302+
#[doc = #cache_ident_doc]
303+
#cache_type
304+
// Cached function
305+
#(#attributes)*
306+
#visibility #signature_no_muts {
307+
use cached::Cached;
308+
let key = #key_convert_block;
309+
#do_set_return_block
310+
}
311+
// Prime cached function
312+
#[doc = #prime_fn_indent_doc]
313+
#[allow(dead_code)]
314+
#visibility #prime_sig {
315+
use cached::Cached;
316+
let key = #key_convert_block;
317+
#prime_do_set_return_block
318+
}
319+
};
320+
321+
expanded.into()
322+
}

cached_proc_macro/src/helpers.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ pub(super) fn make_cache_key_type(
9797
key: &Option<String>,
9898
convert: &Option<String>,
9999
cache_type: &Option<String>,
100-
input_tys: Vec<Box<Type>>,
101-
input_names: &Vec<Box<Pat>>,
100+
input_tys: Vec<Type>,
101+
input_names: &Vec<Pat>,
102102
) -> (TokenStream2, TokenStream2) {
103103
match (key, convert, cache_type) {
104104
(Some(key_str), Some(convert_str), _) => {
@@ -134,14 +134,14 @@ pub(super) fn make_cache_key_type(
134134
// then we need to strip off the `mut` keyword from the
135135
// variable identifiers, so we can refer to arguments `a` and `b`
136136
// instead of `mut a` and `mut b`
137-
pub(super) fn get_input_names(inputs: &Punctuated<FnArg, Comma>) -> Vec<Box<Pat>> {
137+
pub(super) fn get_input_names(inputs: &Punctuated<FnArg, Comma>) -> Vec<Pat> {
138138
inputs
139139
.iter()
140140
.map(|input| match input {
141141
FnArg::Receiver(_) => panic!("methods (functions taking 'self') are not supported"),
142-
FnArg::Typed(pat_type) => match_pattern_type(&pat_type),
142+
FnArg::Typed(pat_type) => *match_pattern_type(&pat_type),
143143
})
144-
.collect::<Vec<Box<Pat>>>()
144+
.collect()
145145
}
146146

147147
pub(super) fn fill_in_attributes(attributes: &mut Vec<Attribute>, cache_fn_doc_extra: String) {
@@ -155,14 +155,14 @@ pub(super) fn fill_in_attributes(attributes: &mut Vec<Attribute>, cache_fn_doc_e
155155
}
156156

157157
// pull out the names and types of the function inputs
158-
pub(super) fn get_input_types(inputs: &Punctuated<FnArg, Comma>) -> Vec<Box<Type>> {
158+
pub(super) fn get_input_types(inputs: &Punctuated<FnArg, Comma>) -> Vec<Type> {
159159
inputs
160160
.iter()
161161
.map(|input| match input {
162162
FnArg::Receiver(_) => panic!("methods (functions taking 'self') are not supported"),
163-
FnArg::Typed(pat_type) => pat_type.ty.clone(),
163+
FnArg::Typed(pat_type) => *pat_type.ty.clone(),
164164
})
165-
.collect::<Vec<Box<Type>>>()
165+
.collect()
166166
}
167167

168168
pub(super) fn get_output_parts(output_ts: &TokenStream) -> Vec<String> {
@@ -173,7 +173,7 @@ pub(super) fn get_output_parts(output_ts: &TokenStream) -> Vec<String> {
173173
proc_macro::TokenTree::Ident(ident) => Some(ident.to_string()),
174174
_ => None,
175175
})
176-
.collect::<Vec<_>>()
176+
.collect()
177177
}
178178

179179
pub(super) fn with_cache_flag_error(output_span: Span, output_type_display: String) -> TokenStream {

0 commit comments

Comments
 (0)