• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 use proc_macro2::TokenStream as TokenStream2;
6 use quote::quote;
7 
8 use crate::utils::{self, FieldInfo, ZeroVecAttrs};
9 use std::collections::HashSet;
10 use syn::spanned::Spanned;
11 use syn::{parse_quote, Data, DataEnum, DataStruct, DeriveInput, Error, Expr, Fields, Ident, Lit};
12 
make_ule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream213 pub 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 
make_ule_enum_impl( name: &Ident, ule_name: &Ident, input: &DeriveInput, enu: &DataEnum, attrs: &ZeroVecAttrs, ) -> TokenStream278 fn 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 
get_expr_int(e: &Expr) -> Option<u64>254 fn 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 
make_ule_struct_impl( name: &Ident, ule_name: &Ident, input: &DeriveInput, struc: &DataStruct, attrs: &ZeroVecAttrs, ) -> TokenStream2262 fn 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 }
374