Skip to content

Commit 431d0b9

Browse files
committed
proc_macro: support fn inputs which are refs: &T and/or Option<&T>
- make_cache_key_type converts keys of Option<&T> to Option<T> - Use the __private ToFullyOwned trait in generated code impl
1 parent f1cfbc1 commit 431d0b9

File tree

9 files changed

+370
-7
lines changed

9 files changed

+370
-7
lines changed

Cargo.toml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,22 @@ async = ["futures", "tokio", "async-trait"]
2323
async_tokio_rt_multi_thread = ["async", "tokio/rt-multi-thread"]
2424
redis_store = ["redis", "r2d2", "serde", "serde_json"]
2525
redis_connection_manager = ["redis_store", "redis/connection-manager"]
26-
redis_async_std = ["redis_store", "async", "redis/aio", "redis/async-std-comp", "redis/tls", "redis/async-std-tls-comp"]
27-
redis_tokio = ["redis_store", "async", "redis/aio", "redis/tokio-comp", "redis/tls", "redis/tokio-native-tls-comp"]
26+
redis_async_std = [
27+
"redis_store",
28+
"async",
29+
"redis/aio",
30+
"redis/async-std-comp",
31+
"redis/tls",
32+
"redis/async-std-tls-comp",
33+
]
34+
redis_tokio = [
35+
"redis_store",
36+
"async",
37+
"redis/aio",
38+
"redis/tokio-comp",
39+
"redis/tls",
40+
"redis/tokio-native-tls-comp",
41+
]
2842
redis_ahash = ["redis_store", "redis/ahash"]
2943
disk_store = ["sled", "serde", "rmp-serde", "directories"]
3044
wasm = ["instant/wasm-bindgen"]
@@ -104,7 +118,7 @@ version = "0.1"
104118

105119
[dev-dependencies]
106120
copy_dir = "0.1.3"
107-
googletest = "0.11.0"
121+
googletest.workspace = true
108122
tempfile = "3.10.1"
109123

110124
[dev-dependencies.async-std]
@@ -117,6 +131,9 @@ version = "1"
117131
[dev-dependencies.serial_test]
118132
version = "3"
119133

134+
[workspace.dependencies]
135+
googletest = "0.11.0"
136+
120137
[workspace]
121138
members = ["cached_proc_macro", "examples/wasm"]
122139

cached_proc_macro/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ quote = "1.0.6"
2121
darling = "0.20.8"
2222
proc-macro2 = "1.0.49"
2323
syn = "2.0.52"
24+
derive-syn-parse = "0.2.0"
25+
26+
[dev-dependencies]
27+
googletest.workspace = true

cached_proc_macro/src/cached.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ pub fn cached(args: TokenStream, input: TokenStream) -> TokenStream {
319319
#visibility #signature_no_muts {
320320
use cached::Cached;
321321
use cached::CloneCached;
322+
use cached::proc_macro::__private::ToFullyOwned as _;
322323
let key = #key_convert_block;
323324
#do_set_return_block
324325
}
@@ -328,6 +329,7 @@ pub fn cached(args: TokenStream, input: TokenStream) -> TokenStream {
328329
#(#attributes)*
329330
#visibility #prime_sig {
330331
use cached::Cached;
332+
use cached::proc_macro::__private::ToFullyOwned as _;
331333
let key = #key_convert_block;
332334
#prime_do_set_return_block
333335
}

cached_proc_macro/src/helpers.rs

Lines changed: 222 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,17 @@ pub(super) fn make_cache_key_type(
115115

116116
(quote! {}, quote! {#key_convert_block})
117117
}
118-
(None, None, _) => (
119-
quote! {(#(#input_tys),*)},
120-
quote! {(#(#input_names.clone()),*)},
121-
),
118+
(None, None, _) => {
119+
let key_tys = input_tys
120+
.into_iter()
121+
.map(convert_option_of_ref_to_option_of_owned_type)
122+
.map(convert_ref_to_owned_type)
123+
.collect::<Vec<Type>>();
124+
(
125+
quote! {(#(#key_tys),*)},
126+
quote! {(#(#input_names.to_fully_owned()),*)},
127+
)
128+
}
122129
(Some(_), None, _) => panic!("key requires convert to be set"),
123130
(None, Some(_), None) => panic!("convert requires key or type to be set"),
124131
}
@@ -218,3 +225,214 @@ pub(super) fn check_with_cache_flag(with_cached_flag: bool, output_string: Strin
218225
&& !output_string.contains("Return")
219226
&& !output_string.contains("cached::Return")
220227
}
228+
229+
use ref_inputs::*;
230+
mod ref_inputs {
231+
use super::*;
232+
233+
pub(super) fn is_option(ty: &Type) -> bool {
234+
if let Type::Path(typepath) = ty {
235+
let segments = &typepath.path.segments;
236+
if segments.len() == 1 {
237+
let segment = segments.first().unwrap();
238+
if segment.ident == "Option" {
239+
return true;
240+
}
241+
} else if segments.len() == 3 {
242+
let segment_idents = segments
243+
.iter()
244+
.map(|s| s.ident.to_string())
245+
.collect::<Vec<_>>();
246+
if segment_idents == ["std", "option", "Option"] {
247+
return true;
248+
}
249+
}
250+
}
251+
false
252+
}
253+
254+
fn option_generic_arg_unchecked(ty: &Type) -> Type {
255+
if let Type::Path(typepath) = ty {
256+
let segment = &typepath
257+
.path
258+
.segments
259+
.last()
260+
.expect("option_generic_arg_unchecked: empty path");
261+
if let PathArguments::AngleBracketed(brackets) = &segment.arguments {
262+
if let Some(syn::GenericArgument::Type(inner_ty)) = brackets.args.first() {
263+
return inner_ty.clone();
264+
}
265+
}
266+
}
267+
panic!("option_generic_arg_unchecked: could not extract inner type");
268+
}
269+
270+
pub(super) fn is_option_of_ref(ty: &Type) -> bool {
271+
if is_option(ty) {
272+
let inner_ty = option_generic_arg_unchecked(ty);
273+
if let Type::Reference(_) = inner_ty {
274+
return true;
275+
}
276+
}
277+
278+
false
279+
}
280+
281+
pub(super) fn convert_ref_to_owned_type(ty: Type) -> Type {
282+
match ty {
283+
Type::Reference(reftype) => *reftype.elem,
284+
_ => ty,
285+
}
286+
}
287+
288+
pub(super) fn convert_option_of_ref_to_option_of_owned_type(ty: Type) -> Type {
289+
if is_option_of_ref(&ty) {
290+
let inner_ty = option_generic_arg_unchecked(&ty);
291+
if let Type::Reference(reftype) = inner_ty {
292+
let elem = *reftype.elem;
293+
return parse_quote! { Option< #elem > };
294+
}
295+
}
296+
ty
297+
}
298+
}
299+
300+
#[cfg(test)]
301+
mod test {
302+
use super::*;
303+
use googletest::{assert_that, matchers::eq};
304+
use syn::parse_quote;
305+
306+
macro_rules! type_test {
307+
($test_name:ident, $target_fn:ident syn_ref, $input_type:ty, $expected:expr) => {
308+
#[googletest::test]
309+
fn $test_name() {
310+
let ty = &parse_quote! { $input_type };
311+
assert_that!($target_fn(ty), eq($expected));
312+
}
313+
};
314+
($test_name:ident, $target_fn:ident syn_owned, $input_type:ty, $expected:expr) => {
315+
#[googletest::test]
316+
fn $test_name() {
317+
let ty = parse_quote! { $input_type };
318+
assert_that!($target_fn(ty), eq($expected));
319+
}
320+
};
321+
}
322+
323+
mod convert_ref_to_owned_type {
324+
use super::*;
325+
326+
type_test! {
327+
returns_the_owned_type_when_given_a_ref_type,
328+
convert_ref_to_owned_type syn_owned,
329+
&T,
330+
parse_quote!{ T }
331+
}
332+
333+
type_test! {
334+
returns_the_same_type_when_given_a_non_ref_type,
335+
convert_ref_to_owned_type syn_owned,
336+
T,
337+
parse_quote!{ T }
338+
}
339+
}
340+
341+
mod convert_option_of_ref_to_option_of_owned_type {
342+
use super::*;
343+
344+
type_test! {
345+
returns_the_owned_option_type_when_given_option_of_ref,
346+
convert_option_of_ref_to_option_of_owned_type syn_owned,
347+
Option<&T>,
348+
parse_quote!{ Option<T> }
349+
}
350+
351+
type_test! {
352+
returns_the_same_type_when_given_a_non_option_type,
353+
convert_option_of_ref_to_option_of_owned_type syn_owned,
354+
T,
355+
parse_quote!{ T }
356+
}
357+
358+
type_test! {
359+
returns_the_same_type_when_given_an_option_of_non_ref_type,
360+
convert_option_of_ref_to_option_of_owned_type syn_owned,
361+
Option<T>,
362+
parse_quote!{ Option<T> }
363+
}
364+
}
365+
366+
mod is_option {
367+
368+
mod when_arg_is_ref {
369+
use super::super::*;
370+
type_test!(returns_true_for_option, is_option syn_ref, Option<&T>, true);
371+
type_test!(
372+
returns_true_for_option_with_fully_qualified_core_path,
373+
is_option syn_ref,
374+
std::option::Option<&T>,
375+
true
376+
);
377+
type_test!(
378+
returns_false_for_custom_type_named_option,
379+
is_option syn_ref,
380+
my_module::Option<&T>,
381+
false
382+
);
383+
}
384+
385+
mod when_arg_is_not_ref {
386+
use super::super::*;
387+
type_test!(returns_true_for_option, is_option syn_ref, Option<T>, true);
388+
type_test!(
389+
returns_true_for_option_with_fully_qualified_core_path,
390+
is_option syn_ref,
391+
std::option::Option<T>,
392+
true
393+
);
394+
type_test!(
395+
returns_false_for_custom_type_named_option,
396+
is_option syn_ref,
397+
my_module::Option<T>,
398+
false
399+
);
400+
type_test!(returns_false_for_simple_type, is_option syn_ref, T, false);
401+
type_test!(returns_false_for_a_generic_type, is_option syn_ref, Vec<T>, false);
402+
}
403+
}
404+
405+
mod is_option_of_ref {
406+
use super::*;
407+
type_test!(
408+
returns_true_for_option_of_ref,
409+
is_option_of_ref syn_ref,
410+
Option<&T>,
411+
true
412+
);
413+
type_test!(
414+
returns_true_for_option_of_ref_with_fully_qualified_core_path,
415+
is_option_of_ref syn_ref,
416+
std::option::Option<&T>,
417+
true
418+
);
419+
type_test!(
420+
returns_false_for_custom_type_named_option_with_ref_generic_arg,
421+
is_option_of_ref syn_ref,
422+
my_module::Option<&T>,
423+
false
424+
);
425+
type_test!(
426+
returns_false_for_option_of_non_ref,
427+
is_option_of_ref syn_ref,
428+
Option<T>,
429+
false
430+
);
431+
type_test!(
432+
returns_false_for_option_of_non_ref_with_fully_qualified_core_path,
433+
is_option_of_ref syn_ref,
434+
std::option::Option<T>,
435+
false
436+
);
437+
}
438+
}

cached_proc_macro/src/io_cached.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,12 @@ pub fn io_cached(args: TokenStream, input: TokenStream) -> TokenStream {
446446
let async_trait = if asyncness.is_some() && !args.disk {
447447
quote! {
448448
use cached::IOCachedAsync;
449+
use cached::proc_macro::__private::ToFullyOwned as _;
449450
}
450451
} else {
451452
quote! {
452453
use cached::IOCached;
454+
use cached::proc_macro::__private::ToFullyOwned as _;
453455
}
454456
};
455457

@@ -475,6 +477,7 @@ pub fn io_cached(args: TokenStream, input: TokenStream) -> TokenStream {
475477
// Cached function
476478
#(#attributes)*
477479
#visibility #signature_no_muts {
480+
use cached::proc_macro::__private::ToFullyOwned as _;
478481
let init = || async { #cache_create };
479482
#async_trait
480483
let key = #key_convert_block;
@@ -504,6 +507,7 @@ pub fn io_cached(args: TokenStream, input: TokenStream) -> TokenStream {
504507
#(#attributes)*
505508
#visibility #signature_no_muts {
506509
use cached::IOCached;
510+
use cached::proc_macro::__private::ToFullyOwned as _;
507511
let key = #key_convert_block;
508512
{
509513
// check if the result is cached
@@ -519,6 +523,7 @@ pub fn io_cached(args: TokenStream, input: TokenStream) -> TokenStream {
519523
#[allow(dead_code)]
520524
#visibility #prime_sig {
521525
use cached::IOCached;
526+
use cached::proc_macro::__private::ToFullyOwned as _;
522527
let key = #key_convert_block;
523528
#do_set_return_block
524529
}

cached_proc_macro_types/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
mod to_fully_owned;
2+
3+
// Not public API.
4+
#[doc(hidden)]
5+
pub mod __private {
6+
pub use super::to_fully_owned::ToFullyOwned;
7+
}
8+
19
/// Used to wrap a function result so callers can see whether the result was cached.
210
#[derive(Clone)]
311
pub struct Return<T> {

0 commit comments

Comments
 (0)