zerovec_derive/
make_ule.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use proc_macro2::TokenStream as TokenStream2;
6use quote::quote;
7
8use crate::utils::{self, FieldInfo, ZeroVecAttrs};
9use std::collections::HashSet;
10use syn::spanned::Spanned;
11use syn::{parse_quote, Data, DataEnum, DataStruct, DeriveInput, Error, Expr, Fields, Ident, Lit};
12
13pub fn make_ule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream2 {
14    if input.generics.type_params().next().is_some()
15        || input.generics.lifetimes().next().is_some()
16        || input.generics.const_params().next().is_some()
17    {
18        return Error::new(
19            input.generics.span(),
20            "#[make_ule] must be applied to a struct without any generics",
21        )
22        .to_compile_error();
23    }
24    let sp = input.span();
25    let attrs = match utils::extract_attributes_common(&mut input.attrs, sp, false) {
26        Ok(val) => val,
27        Err(e) => return e.to_compile_error(),
28    };
29
30    let name = &input.ident;
31
32    let ule_stuff = match input.data {
33        Data::Struct(ref s) => make_ule_struct_impl(name, &ule_name, &input, s, &attrs),
34        Data::Enum(ref e) => make_ule_enum_impl(name, &ule_name, &input, e, &attrs),
35        _ => {
36            return Error::new(input.span(), "#[make_ule] must be applied to a struct")
37                .to_compile_error();
38        }
39    };
40
41    let zmkv = if attrs.skip_kv {
42        quote!()
43    } else {
44        quote!(
45            impl<'a> zerovec::maps::ZeroMapKV<'a> for #name {
46                type Container = zerovec::ZeroVec<'a, #name>;
47                type Slice = zerovec::ZeroSlice<#name>;
48                type GetType = #ule_name;
49                type OwnedType = #name;
50            }
51        )
52    };
53
54    let maybe_debug = if attrs.debug {
55        quote!(
56            impl core::fmt::Debug for #ule_name {
57                fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
58                    let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self);
59                    <#name as core::fmt::Debug>::fmt(&this, f)
60                }
61            }
62        )
63    } else {
64        quote!()
65    };
66
67    quote!(
68        #input
69
70        #ule_stuff
71
72        #maybe_debug
73
74        #zmkv
75    )
76}
77
78fn make_ule_enum_impl(
79    name: &Ident,
80    ule_name: &Ident,
81    input: &DeriveInput,
82    enu: &DataEnum,
83    attrs: &ZeroVecAttrs,
84) -> TokenStream2 {
85    // We could support more int reprs in the future if needed
86    if !utils::ReprInfo::compute(&input.attrs).u8 {
87        return Error::new(
88            input.span(),
89            "#[make_ule] can only be applied to #[repr(u8)] enums",
90        )
91        .to_compile_error();
92    }
93
94    if enu.variants.is_empty() {
95        return Error::new(input.span(), "#[make_ule] cannot be applied to empty enums")
96            .to_compile_error();
97    }
98
99    // the smallest discriminant seen
100    let mut min = None;
101    // the largest discriminant seen
102    let mut max = None;
103    // Discriminants that have not been found in series (we might find them later)
104    let mut not_found = HashSet::new();
105
106    for (i, variant) in enu.variants.iter().enumerate() {
107        if !matches!(variant.fields, Fields::Unit) {
108            // This can be supported in the future, see zerovec/design_doc.md
109            return Error::new(
110                variant.span(),
111                "#[make_ule] can only be applied to enums with dataless variants",
112            )
113            .to_compile_error();
114        }
115
116        if let Some((_, ref discr)) = variant.discriminant {
117            if let Some(n) = get_expr_int(discr) {
118                let n = match u8::try_from(n) {
119                    Ok(n) => n,
120                    Err(_) => {
121                        return Error::new(
122                            variant.span(),
123                            "#[make_ule] only supports discriminants from 0 to 255",
124                        )
125                        .to_compile_error();
126                    }
127                };
128                match min {
129                    Some(x) if x < n => {}
130                    _ => {
131                        min = Some(n);
132                    }
133                }
134                match max {
135                    Some(x) if x >= n => {}
136                    _ => {
137                        let old_max = max.unwrap_or(0u8);
138                        for missing in (old_max + 1)..n {
139                            not_found.insert(missing);
140                        }
141                        max = Some(n);
142                    }
143                }
144
145                not_found.remove(&n);
146
147                // We require explicit discriminants so that it is clear that reordering
148                // fields would be a breaking change. Furthermore, using explicit discriminants helps ensure that
149                // platform-specific C ABI choices do not matter.
150                // We could potentially add in explicit discriminants on the user's behalf in the future, or support
151                // more complicated sets of explicit discriminant values.
152                if n as usize != i {}
153            } else {
154                return Error::new(
155                    discr.span(),
156                    "#[make_ule] must be applied to enums with explicit integer discriminants",
157                )
158                .to_compile_error();
159            }
160        } else {
161            return Error::new(
162                variant.span(),
163                "#[make_ule] must be applied to enums with explicit discriminants",
164            )
165            .to_compile_error();
166        }
167    }
168
169    let not_found = not_found.iter().collect::<Vec<_>>();
170    let min = min.unwrap();
171    let max = max.unwrap();
172
173    if not_found.len() > min as usize {
174        return Error::new(input.span(), format!("#[make_ule] must be applied to enums with discriminants \
175                                                  filling the range from a minimum to a maximum; could not find {not_found:?}"))
176            .to_compile_error();
177    }
178
179    let maybe_ord_derives = if attrs.skip_ord {
180        quote!()
181    } else {
182        quote!(#[derive(Ord, PartialOrd)])
183    };
184
185    let vis = &input.vis;
186
187    let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}");
188
189    // Safety (based on the safety checklist on the ULE trait):
190    //  1. ULE type does not include any uninitialized or padding bytes.
191    //     (achieved by `#[repr(transparent)]` on a type that satisfies this invariant
192    //  2. ULE type is aligned to 1 byte.
193    //     (achieved by `#[repr(transparent)]` on a type that satisfies this invariant)
194    //  3. The impl of validate_bytes() returns an error if any byte is not valid.
195    //     (Guarantees that the byte is in range of the corresponding enum.)
196    //  4. The impl of validate_bytes() returns an error if there are extra bytes.
197    //     (This does not happen since we are backed by 1 byte.)
198    //  5. The other ULE methods use the default impl.
199    //  6. ULE type byte equality is semantic equality
200    quote!(
201        #[repr(transparent)]
202        #[derive(Copy, Clone, PartialEq, Eq)]
203        #maybe_ord_derives
204        #[doc = #doc]
205        #vis struct #ule_name(u8);
206
207        unsafe impl zerovec::ule::ULE for #ule_name {
208            #[inline]
209            fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> {
210                for byte in bytes {
211                    if *byte < #min || *byte > #max {
212                        return Err(zerovec::ule::UleError::parse::<Self>())
213                    }
214                }
215                Ok(())
216            }
217        }
218
219        impl zerovec::ule::AsULE for #name {
220            type ULE = #ule_name;
221
222            fn to_unaligned(self) -> Self::ULE {
223                #ule_name(self as u8)
224            }
225
226            fn from_unaligned(other: Self::ULE) -> Self {
227                // safety: the enum is repr(u8) and can be cast from a u8,
228                // and `#ule_name` guarantees a valid value for this enum.
229                unsafe {
230                    ::core::mem::transmute::<u8, Self>(other.0)
231                }
232            }
233        }
234
235        impl #name {
236            /// Attempt to construct the value from its corresponding integer,
237            /// returning `None` if not possible
238            pub(crate) fn new_from_u8(value: u8) -> Option<Self> {
239                if value <= #max {
240                    Some(zerovec::ule::AsULE::from_unaligned(#ule_name(value)))
241                } else {
242                    None
243                }
244            }
245        }
246    )
247}
248
249fn get_expr_int(e: &Expr) -> Option<u64> {
250    if let Ok(Lit::Int(ref i)) = syn::parse2(quote!(#e)) {
251        return i.base10_parse().ok();
252    }
253
254    None
255}
256
257fn make_ule_struct_impl(
258    name: &Ident,
259    ule_name: &Ident,
260    input: &DeriveInput,
261    struc: &DataStruct,
262    attrs: &ZeroVecAttrs,
263) -> TokenStream2 {
264    if struc.fields.iter().next().is_none() {
265        return Error::new(
266            input.span(),
267            "#[make_ule] must be applied to a non-empty struct",
268        )
269        .to_compile_error();
270    }
271    let sized_fields = FieldInfo::make_list(struc.fields.iter());
272    let field_inits = crate::ule::make_ule_fields(&sized_fields);
273    let field_inits = utils::wrap_field_inits(&field_inits, &struc.fields);
274
275    let semi = utils::semi_for(&struc.fields);
276    let repr_attr = utils::repr_for(&struc.fields);
277    let vis = &input.vis;
278
279    let doc = format!("[`ULE`](zerovec::ule::ULE) type for [`{name}`]");
280
281    let ule_struct: DeriveInput = parse_quote!(
282        #[repr(#repr_attr)]
283        #[derive(Copy, Clone, PartialEq, Eq)]
284        #[doc = #doc]
285        // We suppress the `missing_docs` lint for the fields of the struct.
286        #[allow(missing_docs)]
287        #vis struct #ule_name #field_inits #semi
288    );
289    let derived = crate::ule::derive_impl(&ule_struct);
290
291    let mut as_ule_conversions = vec![];
292    let mut from_ule_conversions = vec![];
293
294    for (i, field) in struc.fields.iter().enumerate() {
295        let ty = &field.ty;
296        let i = syn::Index::from(i);
297        if let Some(ref ident) = field.ident {
298            as_ule_conversions
299                .push(quote!(#ident: <#ty as zerovec::ule::AsULE>::to_unaligned(self.#ident)));
300            from_ule_conversions.push(
301                quote!(#ident: <#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#ident)),
302            );
303        } else {
304            as_ule_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::to_unaligned(self.#i)));
305            from_ule_conversions
306                .push(quote!(<#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#i)));
307        };
308    }
309
310    let as_ule_conversions = utils::wrap_field_inits(&as_ule_conversions, &struc.fields);
311    let from_ule_conversions = utils::wrap_field_inits(&from_ule_conversions, &struc.fields);
312    let asule_impl = quote!(
313        impl zerovec::ule::AsULE for #name {
314            type ULE = #ule_name;
315            fn to_unaligned(self) -> Self::ULE {
316                #ule_name #as_ule_conversions
317            }
318            fn from_unaligned(unaligned: Self::ULE) -> Self {
319                Self #from_ule_conversions
320            }
321        }
322    );
323
324    let maybe_ord_impls = if attrs.skip_ord {
325        quote!()
326    } else {
327        quote!(
328            impl core::cmp::PartialOrd for #ule_name {
329                fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
330                    Some(self.cmp(other))
331                }
332            }
333
334            impl core::cmp::Ord for #ule_name {
335                fn cmp(&self, other: &Self) -> core::cmp::Ordering {
336                    let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self);
337                    let other = <#name as zerovec::ule::AsULE>::from_unaligned(*other);
338                    <#name as core::cmp::Ord>::cmp(&this, &other)
339                }
340            }
341        )
342    };
343
344    let maybe_hash = if attrs.hash {
345        quote!(
346            #[expect(clippy::derive_hash_xor_eq)]
347            impl core::hash::Hash for #ule_name {
348                fn hash<H>(&self, state: &mut H) where H: core::hash::Hasher {
349                    state.write(<#ule_name as zerovec::ule::ULE>::slice_as_bytes(&[*self]));
350                }
351            }
352        )
353    } else {
354        quote!()
355    };
356
357    quote!(
358        #asule_impl
359
360        #ule_struct
361
362        #derived
363
364        #maybe_ord_impls
365
366        #maybe_hash
367    )
368}