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                // safety: the enum is repr(u8) and can be cast to a u8
224                unsafe {
225                    ::core::mem::transmute(self)
226                }
227            }
228
229            fn from_unaligned(other: Self::ULE) -> Self {
230                // safety: the enum is repr(u8) and can be cast from a u8,
231                // and `#ule_name` guarantees a valid value for this enum.
232                unsafe {
233                    ::core::mem::transmute(other)
234                }
235            }
236        }
237
238        impl #name {
239            /// Attempt to construct the value from its corresponding integer,
240            /// returning `None` if not possible
241            pub(crate) fn new_from_u8(value: u8) -> Option<Self> {
242                if value <= #max {
243                    unsafe {
244                        Some(::core::mem::transmute(value))
245                    }
246                } else {
247                    None
248                }
249            }
250        }
251    )
252}
253
254fn get_expr_int(e: &Expr) -> Option<u64> {
255    if let Ok(Lit::Int(ref i)) = syn::parse2(quote!(#e)) {
256        return i.base10_parse().ok();
257    }
258
259    None
260}
261
262fn make_ule_struct_impl(
263    name: &Ident,
264    ule_name: &Ident,
265    input: &DeriveInput,
266    struc: &DataStruct,
267    attrs: &ZeroVecAttrs,
268) -> TokenStream2 {
269    if struc.fields.iter().next().is_none() {
270        return Error::new(
271            input.span(),
272            "#[make_ule] must be applied to a non-empty struct",
273        )
274        .to_compile_error();
275    }
276    let sized_fields = FieldInfo::make_list(struc.fields.iter());
277    let field_inits = crate::ule::make_ule_fields(&sized_fields);
278    let field_inits = utils::wrap_field_inits(&field_inits, &struc.fields);
279
280    let semi = utils::semi_for(&struc.fields);
281    let repr_attr = utils::repr_for(&struc.fields);
282    let vis = &input.vis;
283
284    let doc = format!("[`ULE`](zerovec::ule::ULE) type for [`{name}`]");
285
286    let ule_struct: DeriveInput = parse_quote!(
287        #[repr(#repr_attr)]
288        #[derive(Copy, Clone, PartialEq, Eq)]
289        #[doc = #doc]
290        // We suppress the `missing_docs` lint for the fields of the struct.
291        #[allow(missing_docs)]
292        #vis struct #ule_name #field_inits #semi
293    );
294    let derived = crate::ule::derive_impl(&ule_struct);
295
296    let mut as_ule_conversions = vec![];
297    let mut from_ule_conversions = vec![];
298
299    for (i, field) in struc.fields.iter().enumerate() {
300        let ty = &field.ty;
301        let i = syn::Index::from(i);
302        if let Some(ref ident) = field.ident {
303            as_ule_conversions
304                .push(quote!(#ident: <#ty as zerovec::ule::AsULE>::to_unaligned(self.#ident)));
305            from_ule_conversions.push(
306                quote!(#ident: <#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#ident)),
307            );
308        } else {
309            as_ule_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::to_unaligned(self.#i)));
310            from_ule_conversions
311                .push(quote!(<#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#i)));
312        };
313    }
314
315    let as_ule_conversions = utils::wrap_field_inits(&as_ule_conversions, &struc.fields);
316    let from_ule_conversions = utils::wrap_field_inits(&from_ule_conversions, &struc.fields);
317    let asule_impl = quote!(
318        impl zerovec::ule::AsULE for #name {
319            type ULE = #ule_name;
320            fn to_unaligned(self) -> Self::ULE {
321                #ule_name #as_ule_conversions
322            }
323            fn from_unaligned(unaligned: Self::ULE) -> Self {
324                Self #from_ule_conversions
325            }
326        }
327    );
328
329    let maybe_ord_impls = if attrs.skip_ord {
330        quote!()
331    } else {
332        quote!(
333            impl core::cmp::PartialOrd for #ule_name {
334                fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
335                    Some(self.cmp(other))
336                }
337            }
338
339            impl core::cmp::Ord for #ule_name {
340                fn cmp(&self, other: &Self) -> core::cmp::Ordering {
341                    let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self);
342                    let other = <#name as zerovec::ule::AsULE>::from_unaligned(*other);
343                    <#name as core::cmp::Ord>::cmp(&this, &other)
344                }
345            }
346        )
347    };
348
349    let maybe_hash = if attrs.hash {
350        quote!(
351            #[allow(clippy::derive_hash_xor_eq)]
352            impl core::hash::Hash for #ule_name {
353                fn hash<H>(&self, state: &mut H) where H: core::hash::Hasher {
354                    state.write(<#ule_name as zerovec::ule::ULE>::slice_as_bytes(&[*self]));
355                }
356            }
357        )
358    } else {
359        quote!()
360    };
361
362    quote!(
363        #asule_impl
364
365        #ule_struct
366
367        #derived
368
369        #maybe_ord_impls
370
371        #maybe_hash
372    )
373}