1use darling::FromMeta;
2use darling::ast::NestedMeta;
3use proc_macro2::TokenStream;
4use syn::spanned::Spanned;
5
6use crate::utils::SetSpan;
7
8#[derive(darling::FromMeta)]
9struct Args {
10 name: syn::LitStr,
11 version: syn::Expr,
12 methods: DataTypeMethods,
13}
14
15enum SetOrPath {
16 Set(syn::Path),
17 Path(syn::Path),
18}
19
20impl SetOrPath {
21 fn path(&self, name: &syn::Ident, generics: &syn::Generics) -> syn::Path {
22 match self {
23 Self::Set(func) => {
24 let ty = generics.type_params();
25 let path: syn::Path = syn::parse_quote! { #name::<#(#ty),*>::#func };
26 path.set_span(func.span())
27 }
28 Self::Path(path) => path.clone(),
29 }
30 }
31}
32
33impl darling::FromMeta for SetOrPath {
34 fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
35 match item {
36 syn::Meta::Path(path) => Ok(Self::Set(path.clone())),
37 syn::Meta::NameValue(value) => {
38 let expr = &value.value;
39 Ok(Self::Path(syn::parse_quote!(#expr)))
40 }
41 _ => todo!(),
42 }
43 }
44}
45
46#[derive(darling::FromMeta)]
47struct DataTypeMethods {
48 rdb_load: SetOrPath,
49 rdb_save: SetOrPath,
50 aof_rewrite: Option<SetOrPath>,
51 mem_usage: Option<SetOrPath>,
52 digest: Option<SetOrPath>,
53 aux_load: Option<SetOrPath>,
54 aux_save: Option<SetOrPath>,
55 free_effort: Option<SetOrPath>,
56 unlink: Option<SetOrPath>,
57 copy: Option<SetOrPath>,
58 defrag: Option<SetOrPath>,
59 mem_usage2: Option<SetOrPath>,
60 free_effort2: Option<SetOrPath>,
61 unlink2: Option<SetOrPath>,
62 copy2: Option<SetOrPath>,
63 aux_save2: Option<SetOrPath>,
64}
65
66macro_rules! func_to_tokens {
67 (
68 $fn_ident:ident
69 $trait_ident:ident
70 $fn_ty:ty
71 ) => {
72 fn $fn_ident(&self, name: &syn::Ident, generics: &syn::Generics) -> TokenStream {
73 func_to_tokens!(@body; name, generics, self.$fn_ident, $trait_ident, $fn_ty)
74 }
75 };
76 (
77 opt $fn_ident:ident
78 $trait_ident:ident
79 $fn_ty:ty
80 ) => {
81 fn $fn_ident(&self, name: &syn::Ident, generics: &syn::Generics) -> TokenStream {
82 match &self.$fn_ident {
83 Some(value) => func_to_tokens!(@body; name, generics, value, $trait_ident, $fn_ty),
84 None => quote::quote!(::core::option::Option::None)
85 }
86 }
87 };
88 (
89 @body; $name:ident, $generics:ident, $path:expr, $trait_ident:ident, $fn_ty:ty
90 ) => {{
91 let (imp, ty, wh) = $generics.split_for_impl();
92 let path = $path.path($name, $generics);
93 quote::quote!({
94 struct Tmp;
95 impl #imp redis_module_ext::data_type::$trait_ident<Tmp> for #$name #ty #wh {
96 const FN: redis_module_ext::data_type::$fn_ty = #path;
97 }
98
99 <#$name #ty as redis_module_ext::data_type::$trait_ident<Tmp>>::extern_fn()
100 })
101 }}
102}
103
104impl DataTypeMethods {
105 func_to_tokens!(rdb_load RdbLoad RdbLoadFn<Self>);
106
107 func_to_tokens!(rdb_save RdbSave RdbSaveFn<Self>);
108
109 func_to_tokens!(opt aof_rewrite AofRewrite AofRewriteFn<Self>);
110
111 func_to_tokens!(opt mem_usage MemUsage MemUsageFn<Self>);
112
113 func_to_tokens!(opt digest Digest DigestFn<Self>);
114
115 func_to_tokens!(opt aux_load AuxLoad AuxLoadFn);
116
117 func_to_tokens!(opt aux_save AuxSave AuxSaveFn);
118
119 func_to_tokens!(opt free_effort FreeEffort FreeEffortFn<Self>);
120
121 func_to_tokens!(opt unlink Unlink UnlinkFn<Self>);
122
123 func_to_tokens!(opt copy Copy CopyFn<Self>);
124
125 func_to_tokens!(opt defrag Defrag DefragFn<Self>);
126
127 func_to_tokens!(opt mem_usage2 MemUsage2 MemUsage2Fn<Self>);
128
129 func_to_tokens!(opt free_effort2 FreeEffort2 FreeEffort2Fn<Self>);
130
131 func_to_tokens!(opt unlink2 Unlink2 Unlink2Fn<Self>);
132
133 func_to_tokens!(opt copy2 Copy2 Copy2Fn<Self>);
134
135 func_to_tokens!(opt aux_save2 AuxSave2 AuxSave2Fn);
136}
137
138pub fn macro_impl(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
139 let attr_args = NestedMeta::parse_meta_list(attr)?;
140
141 let args = Args::from_list(&attr_args)?;
142
143 if args.name.value().len() != 9 {
144 return Err(syn::Error::new(
145 args.name.span(),
146 format!("names must be exactly 9 characters long, found: {}", args.name.value().len()),
147 ));
148 }
149
150 let item = syn::parse2::<syn::Item>(item)?;
151
152 let (name, generics) = match &item {
153 syn::Item::Struct(item) => (&item.ident, &item.generics),
154 syn::Item::Enum(item) => (&item.ident, &item.generics),
155 syn::Item::Type(item) => (&item.ident, &item.generics),
156 syn::Item::Union(item) => (&item.ident, &item.generics),
157 _ => {
158 return Err(syn::Error::new_spanned(
159 item,
160 "data type can only be made from a struct / enum / type / union",
161 ));
162 }
163 };
164
165 let type_name = &args.name;
166 let type_version = &args.version;
167
168 let rdb_load = args.methods.rdb_load(name, generics);
169 let rdb_save = args.methods.rdb_save(name, generics);
170 let aof_rewrite = args.methods.aof_rewrite(name, generics);
171 let mem_usage = args.methods.mem_usage(name, generics);
172 let digest = args.methods.digest(name, generics);
173 let aux_load = args.methods.aux_load(name, generics);
174 let aux_save = args.methods.aux_save(name, generics);
175 let free_effort = args.methods.free_effort(name, generics);
176 let unlink = args.methods.unlink(name, generics);
177 let copy = args.methods.copy(name, generics);
178 let defrag = args.methods.defrag(name, generics);
179 let mem_usage2 = args.methods.mem_usage2(name, generics);
180 let free_effort2 = args.methods.free_effort2(name, generics);
181 let unlink2 = args.methods.unlink2(name, generics);
182 let copy2 = args.methods.copy2(name, generics);
183 let aux_save2 = args.methods.aux_save2(name, generics);
184
185 let (imp, ty, wh) = generics.split_for_impl();
186
187 Ok(quote::quote! {
188 #item
189
190 const _: () = {
191 impl #imp redis_module_ext::data_type::RedisDataType for #name #ty #wh {
192 const NAME: &'static str = #type_name;
193 const VERSION: i32 = #type_version;
194
195 fn module_methods(_: &::redis_module_ext::redis::Context) -> redis_module_ext::raw::RedisModuleTypeMethods {
196 redis_module_ext::raw::RedisModuleTypeMethods {
197 version: redis_module_ext::raw::REDISMODULE_TYPE_METHOD_VERSION as u64,
198 aux_save_triggers: 0,
199 rdb_load: #rdb_load,
200 rdb_save: #rdb_save,
201 aof_rewrite: #aof_rewrite,
202 mem_usage: #mem_usage,
203 digest: #digest,
204 free: {
205 unsafe extern "C" fn free<T>(value: *mut ::std::os::raw::c_void) {
206 drop(unsafe { ::std::boxed::Box::from_raw(value.cast::<T>()) });
207 }
208
209 Some(free::<#name #ty>)
210 },
211 aux_load: #aux_load,
212 aux_save: #aux_save,
213 free_effort: #free_effort,
214 unlink: #unlink,
215 copy: #copy,
216 defrag: #defrag,
217 mem_usage2: #mem_usage2,
218 free_effort2: #free_effort2,
219 unlink2: #unlink2,
220 copy2: #copy2,
221 aux_save2: #aux_save2,
222 }
223 }
224 }
225 };
226 })
227}