Skip to main content

redb_derive/
lib.rs

1#![deny(clippy::all, clippy::pedantic, clippy::disallowed_methods)]
2#![allow(
3    clippy::must_use_candidate,
4    clippy::redundant_closure_for_method_calls,
5    clippy::similar_names,
6    clippy::too_many_lines
7)]
8
9use proc_macro::TokenStream;
10use quote::quote;
11use syn::{Data, DeriveInput, Fields, GenericParam, Ident, parse_macro_input};
12
13// All paths in the generated code must be fully qualified (`::std::...`, `#redb::...`, and
14// `<T as #redb::Value>::...` for trait methods, where `#redb` is the path returned by
15// `redb_crate_path_for()`). Method-call syntax and bare paths resolve inherent items before
16// trait items, so a field type with an inherent `from_bytes` or `as_bytes` (e.g. `uuid::Uuid`)
17// would otherwise hijack the generated encoding, and names shadowed at the derive site would
18// change what the code means.
19
20// The path of the redb crate in generated code: `::redb`, unless an explicit
21// `#[redb(crate = "path")]` on the deriving struct names it otherwise, as when the dependency
22// is renamed (`my_redb = { package = "redb" }`) or the implementations are derived against one
23// of several versions of the redb package.
24fn redb_crate_path_for(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
25    let mut path = None;
26    for attr in &input.attrs {
27        if !attr.path().is_ident("redb") {
28            continue;
29        }
30        attr.parse_nested_meta(|meta| {
31            if meta.path.is_ident("crate") {
32                let value: syn::LitStr = meta.value()?.parse()?;
33                path = Some(value.parse::<syn::Path>()?);
34                Ok(())
35            } else {
36                Err(meta.error("expected `#[redb(crate = \"...\")]`"))
37            }
38        })?;
39    }
40    Ok(path.map_or_else(|| quote! { ::redb }, |path| quote! { #path }))
41}
42
43/// Derives `redb::Key` for a struct whose fields all implement `Key`.
44///
45/// The generated implementation refers to the redb crate as `::redb`. When it should be
46/// generated for a crate under another name -- a renamed dependency
47/// (`my_redb = { package = "redb" }`), or one of several versions of redb -- name that crate
48/// with `#[redb(crate = "my_redb")]`. For a struct with fields, the generated implementation
49/// uses `TypeName` API that redb makes public from 3.0 on; a unit struct works with older
50/// versions as well. For a struct with exactly one field, the derived `Value` implementation
51/// forwards the field's `Value::NICHE`, so such a struct needs a version that has that
52/// constant. The tagging of user-defined field types in the composed type name follows
53/// the named version's classification: redb 4.1 and older classify composites (`Option`,
54/// `Vec`, tuples, arrays) of user-defined types as built-in, so such composite fields render
55/// untagged there, and their names collide exactly where that version's own type names do.
56#[proc_macro_derive(Key, attributes(redb))]
57pub fn derive_key(input: TokenStream) -> TokenStream {
58    let input = parse_macro_input!(input as DeriveInput);
59
60    match generate_key_impl(&input) {
61        Ok(tokens) => tokens.into(),
62        Err(err) => err.to_compile_error().into(),
63    }
64}
65
66fn generate_key_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
67    let Data::Struct(_) = &input.data else {
68        return Err(syn::Error::new_spanned(
69            input,
70            "Key can only be derived for structs",
71        ));
72    };
73
74    let name = &input.ident;
75    let generics = &input.generics;
76    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
77    let redb = redb_crate_path_for(input)?;
78
79    Ok(quote! {
80        impl #impl_generics #redb::Key for #name #ty_generics #where_clause {
81            fn compare(
82                data1: &[::std::primitive::u8],
83                data2: &[::std::primitive::u8],
84            ) -> ::std::cmp::Ordering {
85                let value1 = <Self as #redb::Value>::from_bytes(data1);
86                let value2 = <Self as #redb::Value>::from_bytes(data2);
87                ::std::cmp::Ord::cmp(&value1, &value2)
88            }
89        }
90    })
91}
92
93/// Derives `redb::Value` for a struct whose fields all implement `Value`.
94///
95/// A struct with exactly one field is encoded exactly as that field, like `(T,)`, so it
96/// forwards the field's `Value::NICHE`, and `Option` of it is encoded as `Option` of the field
97/// is. Other structs declare no niche.
98///
99/// The generated implementation refers to the redb crate as `::redb`. When it should be
100/// generated for a crate under another name -- a renamed dependency
101/// (`my_redb = { package = "redb" }`), or one of several versions of redb -- name that crate
102/// with `#[redb(crate = "my_redb")]`. For a struct with fields, the generated implementation
103/// uses `TypeName` API that redb makes public from 3.0 on; a unit struct works with older
104/// versions as well. For a struct with exactly one field, the derived `Value` implementation
105/// forwards the field's `Value::NICHE`, so such a struct needs a version that has that
106/// constant. The tagging of user-defined field types in the composed type name follows
107/// the named version's classification: redb 4.1 and older classify composites (`Option`,
108/// `Vec`, tuples, arrays) of user-defined types as built-in, so such composite fields render
109/// untagged there, and their names collide exactly where that version's own type names do.
110#[proc_macro_derive(Value, attributes(redb))]
111pub fn derive_value(input: TokenStream) -> TokenStream {
112    let input = parse_macro_input!(input as DeriveInput);
113
114    match generate_value_impl(&input) {
115        Ok(tokens) => tokens.into(),
116        Err(err) => err.to_compile_error().into(),
117    }
118}
119
120fn generate_value_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
121    let Data::Struct(data_struct) = &input.data else {
122        return Err(syn::Error::new_spanned(
123            input,
124            "Value can only be derived for structs",
125        ));
126    };
127
128    let name = &input.ident;
129    let generics = &input.generics;
130    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
131
132    let self_type = generate_self_type(name, generics)?;
133    let redb = redb_crate_path_for(input)?;
134
135    let type_name_impl = generate_type_name(name, &data_struct.fields, &redb);
136    let as_bytes_impl = generate_as_bytes(&data_struct.fields, &redb);
137    let from_bytes_impl = generate_from_bytes(name, &data_struct.fields, &redb);
138    let fixed_width_impl = generate_fixed_width(&data_struct.fields, &redb);
139    let niche_impl = generate_niche(&data_struct.fields, &redb);
140
141    Ok(quote! {
142        impl #impl_generics #redb::Value for #name #ty_generics #where_clause {
143            type SelfType<'__redb_a> = #self_type
144            where
145                Self: '__redb_a;
146            type AsBytes<'__redb_a> = ::std::vec::Vec<::std::primitive::u8>
147            where
148                Self: '__redb_a;
149
150            #niche_impl
151
152            fn fixed_width() -> ::std::option::Option<::std::primitive::usize> {
153                #fixed_width_impl
154            }
155
156            fn from_bytes<'__redb_a>(
157                data: &'__redb_a [::std::primitive::u8],
158            ) -> Self::SelfType<'__redb_a>
159            where
160                Self: '__redb_a,
161            {
162                #from_bytes_impl
163            }
164
165            fn as_bytes<'__redb_a, '__redb_b: '__redb_a>(
166                value: &'__redb_a Self::SelfType<'__redb_b>,
167            ) -> Self::AsBytes<'__redb_a>
168            where
169                Self: '__redb_b,
170            {
171                #as_bytes_impl
172            }
173
174            fn type_name() -> #redb::TypeName {
175                #type_name_impl
176            }
177        }
178    })
179}
180
181fn generate_self_type(
182    name: &syn::Ident,
183    generics: &syn::Generics,
184) -> syn::Result<proc_macro2::TokenStream> {
185    if generics.params.is_empty() {
186        Ok(quote! { #name })
187    } else {
188        let mut params = vec![];
189        for param in &generics.params {
190            match param {
191                GenericParam::Lifetime(_) => params.push(quote! { '__redb_a }),
192                GenericParam::Type(type_param) => {
193                    return Err(syn::Error::new_spanned(
194                        type_param,
195                        "Value derivation is not implemented for structs with type parameters",
196                    ));
197                }
198                GenericParam::Const(const_param) => {
199                    return Err(syn::Error::new_spanned(
200                        const_param,
201                        "Value derivation is not implemented for structs with const parameters",
202                    ));
203                }
204            }
205        }
206
207        Ok(quote! { #name<#(#params),*> })
208    }
209}
210
211// Renders the name of one field's type for embedding in the struct's `TypeName`. A user-defined
212// type may share its name string with a built-in type (e.g. a user type named "String"), and only
213// the classification of its `TypeName` tells the two apart, so user-defined field types are
214// tagged to keep the composed names distinct. Built-in field types render as the bare name,
215// which keeps the `TypeName` of structs made only of built-in types unchanged.
216//
217// The classification is read by rebuilding the `TypeName` through `TypeName::new` -- the
218// user-defined constructor -- and comparing: `TypeName` equality is exactly (classification,
219// name), so the rebuilt name is equal iff the field's classification is user-defined. redb's
220// `TypeName::is_user_defined()` would say the same, but it is crate-private, and `new`,
221// `name()`, and `PartialEq` reach back to redb 3.0, which `#[redb(crate = "...")]` may name
222// (`name()` is private before that, so field-bearing structs already floor there).
223//
224// The classification is whatever the named redb reports: 4.1 and older classify composites of
225// user-defined types (`Option<UserType>`, say) as built-in, so those fields render untagged
226// and collide exactly where that version's own type names do. Recursing into the field's type
227// syntax instead would misrender aliases (`type A = Option<UserType>`) and change composed
228// names between redb versions, breaking existing tables on upgrade.
229fn render_field_type_name(
230    field_type: &syn::Type,
231    redb: &proc_macro2::TokenStream,
232) -> proc_macro2::TokenStream {
233    quote! {
234        {
235            let type_name = <#field_type as #redb::Value>::type_name();
236            if type_name == #redb::TypeName::new(type_name.name()) {
237                ::std::format!("{}#user", type_name.name())
238            } else {
239                ::std::borrow::ToOwned::to_owned(type_name.name())
240            }
241        }
242    }
243}
244
245fn generate_type_name(
246    struct_name: &Ident,
247    fields: &Fields,
248    redb: &proc_macro2::TokenStream,
249) -> proc_macro2::TokenStream {
250    match fields {
251        Fields::Named(fields_named) => {
252            let field_strings: Vec<_> = fields_named
253                .named
254                .iter()
255                .map(|field| {
256                    let field_name = field.ident.as_ref().unwrap();
257                    let rendered_type = render_field_type_name(&field.ty, redb);
258                    quote! {
259                        ::std::format!("{}: {}", ::std::stringify!(#field_name), #rendered_type)
260                    }
261                })
262                .collect();
263
264            if field_strings.is_empty() {
265                quote! {
266                    #redb::TypeName::new(&::std::format!("{} {{}}",
267                        ::std::stringify!(#struct_name),
268                    ))
269                }
270            } else {
271                quote! {
272                    #redb::TypeName::new(&::std::format!("{} {{{}}}",
273                        ::std::stringify!(#struct_name),
274                        [#(#field_strings),*].join(", ")
275                    ))
276                }
277            }
278        }
279        Fields::Unnamed(fields_unnamed) => {
280            let field_strings: Vec<_> = fields_unnamed
281                .unnamed
282                .iter()
283                .map(|field| render_field_type_name(&field.ty, redb))
284                .collect();
285
286            if field_strings.is_empty() {
287                quote! {
288                    #redb::TypeName::new(&::std::format!("{}()",
289                        ::std::stringify!(#struct_name),
290                    ))
291                }
292            } else {
293                quote! {
294                    #redb::TypeName::new(&::std::format!("{}({})",
295                        ::std::stringify!(#struct_name),
296                        [#(#field_strings),*].join(", ")
297                    ))
298                }
299            }
300        }
301        Fields::Unit => {
302            quote! {
303                #redb::TypeName::new(::std::stringify!(#struct_name))
304            }
305        }
306    }
307}
308
309fn get_field_types(fields: &Fields) -> Vec<syn::Type> {
310    match fields {
311        Fields::Named(fields_named) => fields_named
312            .named
313            .iter()
314            .map(|field| &field.ty)
315            .cloned()
316            .collect(),
317        Fields::Unnamed(fields_unnamed) => fields_unnamed
318            .unnamed
319            .iter()
320            .map(|field| &field.ty)
321            .cloned()
322            .collect(),
323        Fields::Unit => vec![],
324    }
325}
326
327// A struct with one field is encoded exactly as that field, like `(T,)`, so the field's niche
328// is its niche too. Any other struct keeps the trait's default, by declaring nothing. The
329// generated code refers to `Value::NICHE`, so a one-field struct can only be derived against a
330// redb that has it.
331fn generate_niche(fields: &Fields, redb: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
332    let field_types = get_field_types(fields);
333    let [field_type] = field_types.as_slice() else {
334        return quote! {};
335    };
336    quote! {
337        const NICHE: ::std::option::Option<&'static [::std::primitive::u8]> =
338            <#field_type as #redb::Value>::NICHE;
339    }
340}
341
342fn generate_fixed_width(
343    fields: &Fields,
344    redb: &proc_macro2::TokenStream,
345) -> proc_macro2::TokenStream {
346    let field_types = get_field_types(fields);
347    quote! {
348        let mut total_width = 0usize;
349        #(
350            total_width += <#field_types as #redb::Value>::fixed_width()?;
351        )*
352        ::std::option::Option::Some(total_width)
353    }
354}
355
356fn generate_as_bytes(fields: &Fields, redb: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
357    let field_types = get_field_types(fields);
358    let field_accessors = match fields {
359        Fields::Named(fields_named) => fields_named
360            .named
361            .iter()
362            .map(|field| {
363                let name = &field.ident;
364                quote! { #name }
365            })
366            .collect(),
367        Fields::Unnamed(_) => (0..field_types.len())
368            .map(|i| {
369                let index = syn::Index::from(i);
370                quote! { #index }
371            })
372            .collect(),
373        Fields::Unit => Vec::new(),
374    };
375
376    let num_fields = field_types.len();
377
378    if num_fields == 0 {
379        quote! { ::std::vec::Vec::new() }
380    } else if num_fields == 1 {
381        let field_accessor = &field_accessors[0];
382        let field_type = &field_types[0];
383        quote! {
384            {
385                let field_bytes = <#field_type as #redb::Value>::as_bytes(&value.#field_accessor);
386                let bytes: &[::std::primitive::u8] = ::std::convert::AsRef::as_ref(&field_bytes);
387                bytes.to_vec()
388            }
389        }
390    } else {
391        let field_types_except_last = &field_types[..num_fields - 1];
392        let field_accessors_except_last = &field_accessors[..num_fields - 1];
393
394        quote! {
395            {
396                let mut result = ::std::vec::Vec::new();
397
398                #(
399                    if <#field_types_except_last as #redb::Value>::fixed_width().is_none() {
400                        let field_bytes = <#field_types_except_last as #redb::Value>::as_bytes(
401                            &value.#field_accessors_except_last,
402                        );
403                        let bytes: &[::std::primitive::u8] =
404                            ::std::convert::AsRef::as_ref(&field_bytes);
405                        let len = bytes.len();
406                        if len < 254 {
407                            result.push(
408                                <::std::primitive::u8 as ::std::convert::TryFrom<
409                                    ::std::primitive::usize,
410                                >>::try_from(len)
411                                .unwrap(),
412                            );
413                        } else if let ::std::result::Result::Ok(u16_len) =
414                            <::std::primitive::u16 as ::std::convert::TryFrom<
415                                ::std::primitive::usize,
416                            >>::try_from(len)
417                        {
418                            result.push(254u8);
419                            result.extend_from_slice(&u16_len.to_le_bytes());
420                        } else {
421                            let u32_len = <::std::primitive::u32 as ::std::convert::TryFrom<
422                                ::std::primitive::usize,
423                            >>::try_from(len)
424                            .unwrap();
425                            result.push(255u8);
426                            result.extend_from_slice(&u32_len.to_le_bytes());
427                        }
428                    }
429                )*
430
431                #(
432                    {
433                        let field_bytes = <#field_types as #redb::Value>::as_bytes(
434                            &value.#field_accessors,
435                        );
436                        let bytes: &[::std::primitive::u8] =
437                            ::std::convert::AsRef::as_ref(&field_bytes);
438                        result.extend_from_slice(bytes);
439                    }
440                )*
441
442                result
443            }
444        }
445    }
446}
447
448fn generate_from_bytes(
449    name: &Ident,
450    fields: &Fields,
451    redb: &proc_macro2::TokenStream,
452) -> proc_macro2::TokenStream {
453    let field_types = get_field_types(fields);
454    let field_vars: Vec<_> = (0..field_types.len())
455        .map(|i| quote::format_ident!("field_{}", i))
456        .collect();
457    let num_fields = field_types.len();
458
459    let body = if num_fields == 0 {
460        quote! {}
461    } else if num_fields == 1 {
462        let field_var = &field_vars[0];
463        let field_type = &field_types[0];
464        quote! {
465            let #field_var = <#field_type as #redb::Value>::from_bytes(data);
466        }
467    } else {
468        let field_types_except_last = &field_types[..num_fields - 1];
469        let field_vars_except_last = &field_vars[..num_fields - 1];
470        let last_field_var = field_vars.last();
471        let last_field_type = field_types.last();
472
473        quote! {
474            let mut offset = 0usize;
475            let mut var_lengths = ::std::vec::Vec::new();
476
477            #(
478                if <#field_types_except_last as #redb::Value>::fixed_width().is_none() {
479                    let (len, bytes_read) = match data[offset] {
480                        0u8..=253u8 => (data[offset] as ::std::primitive::usize, 1usize),
481                        254u8 => (
482                            ::std::primitive::u16::from_le_bytes([
483                                data[offset + 1],
484                                data[offset + 2],
485                            ]) as ::std::primitive::usize,
486                            3usize,
487                        ),
488                        255u8 => (
489                            ::std::primitive::u32::from_le_bytes([
490                                data[offset + 1],
491                                data[offset + 2],
492                                data[offset + 3],
493                                data[offset + 4],
494                            ]) as ::std::primitive::usize,
495                            5usize,
496                        ),
497                    };
498                    var_lengths.push(len);
499                    offset += bytes_read;
500                }
501            )*
502
503            let mut var_index = 0usize;
504            #(
505                let #field_vars_except_last = if let ::std::option::Option::Some(fixed_width) =
506                    <#field_types_except_last as #redb::Value>::fixed_width()
507                {
508                    let field_data = &data[offset..offset + fixed_width];
509                    offset += fixed_width;
510                    <#field_types_except_last as #redb::Value>::from_bytes(field_data)
511                } else {
512                    let len = var_lengths[var_index];
513                    let field_data = &data[offset..offset + len];
514                    offset += len;
515                    var_index += 1;
516                    <#field_types_except_last as #redb::Value>::from_bytes(field_data)
517                };
518            )*
519
520            let #last_field_var = if let ::std::option::Option::Some(fixed_width) =
521                <#last_field_type as #redb::Value>::fixed_width()
522            {
523                let field_data = &data[offset..offset + fixed_width];
524                <#last_field_type as #redb::Value>::from_bytes(field_data)
525            } else {
526                <#last_field_type as #redb::Value>::from_bytes(&data[offset..])
527            };
528        }
529    };
530    match fields {
531        Fields::Named(fields_named) => {
532            let field_names: Vec<_> = fields_named
533                .named
534                .iter()
535                .map(|field| &field.ident)
536                .collect();
537
538            quote! {
539                {
540                    #body
541                    #name {
542                        #(#field_names: #field_vars),*
543                    }
544                }
545            }
546        }
547        Fields::Unnamed(_) => {
548            quote! {
549                {
550                    #body
551                    #name(#(#field_vars),*)
552                }
553            }
554        }
555        Fields::Unit => {
556            quote! { #name }
557        }
558    }
559}