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 ).
45use proc_macro2::TokenStream as TokenStream2;
6use quote::quote;
78use 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};
1213pub fn make_ule_impl(ule_name: Ident, mut input: DeriveInput) -> TokenStream2 {
14if input.generics.type_params().next().is_some()
15 || input.generics.lifetimes().next().is_some()
16 || input.generics.const_params().next().is_some()
17 {
18return Error::new(
19 input.generics.span(),
20"#[make_ule] must be applied to a struct without any generics",
21 )
22 .to_compile_error();
23 }
24let sp = input.span();
25let attrs = match utils::extract_attributes_common(&mut input.attrs, sp, false) {
26Ok(val) => val,
27Err(e) => return e.to_compile_error(),
28 };
2930let name = &input.ident;
3132let 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_ => {
36return Error::new(input.span(), "#[make_ule] must be applied to a struct")
37 .to_compile_error();
38 }
39 };
4041let zmkv = if attrs.skip_kv {
42quote!()
43 } else {
44quote!(
45impl<'a> zerovec::maps::ZeroMapKV<'a> for #name {
46type Container = zerovec::ZeroVec<'a, #name>;
47type Slice = zerovec::ZeroSlice<#name>;
48type GetType = #ule_name;
49type OwnedType = #name;
50 }
51 )
52 };
5354let maybe_debug = if attrs.debug {
55quote!(
56impl core::fmt::Debug for #ule_name {
57fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
58let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self);
59 <#name as core::fmt::Debug>::fmt(&this, f)
60 }
61 }
62 )
63 } else {
64quote!()
65 };
6667quote!(
68 #input
6970 #ule_stuff
7172 #maybe_debug
7374 #zmkv
75 )
76}
7778fn 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
86if !utils::ReprInfo::compute(&input.attrs).u8 {
87return Error::new(
88 input.span(),
89"#[make_ule] can only be applied to #[repr(u8)] enums",
90 )
91 .to_compile_error();
92 }
9394if enu.variants.is_empty() {
95return Error::new(input.span(), "#[make_ule] cannot be applied to empty enums")
96 .to_compile_error();
97 }
9899// the smallest discriminant seen
100let mut min = None;
101// the largest discriminant seen
102let mut max = None;
103// Discriminants that have not been found in series (we might find them later)
104let mut not_found = HashSet::new();
105106for (i, variant) in enu.variants.iter().enumerate() {
107if !matches!(variant.fields, Fields::Unit) {
108// This can be supported in the future, see zerovec/design_doc.md
109return Error::new(
110 variant.span(),
111"#[make_ule] can only be applied to enums with dataless variants",
112 )
113 .to_compile_error();
114 }
115116if let Some((_, ref discr)) = variant.discriminant {
117if let Some(n) = get_expr_int(discr) {
118let n = match u8::try_from(n) {
119Ok(n) => n,
120Err(_) => {
121return Error::new(
122 variant.span(),
123"#[make_ule] only supports discriminants from 0 to 255",
124 )
125 .to_compile_error();
126 }
127 };
128match min {
129Some(x) if x < n => {}
130_ => {
131 min = Some(n);
132 }
133 }
134match max {
135Some(x) if x >= n => {}
136_ => {
137let old_max = max.unwrap_or(0u8);
138for missing in (old_max + 1)..n {
139 not_found.insert(missing);
140 }
141 max = Some(n);
142 }
143 }
144145 not_found.remove(&n);
146147// 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.
152if n as usize != i {}
153 } else {
154return 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 {
161return Error::new(
162 variant.span(),
163"#[make_ule] must be applied to enums with explicit discriminants",
164 )
165 .to_compile_error();
166 }
167 }
168169let not_found = not_found.iter().collect::<Vec<_>>();
170let min = min.unwrap();
171let max = max.unwrap();
172173if not_found.len() > min as usize {
174return 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 }
178179let maybe_ord_derives = if attrs.skip_ord {
180quote!()
181 } else {
182quote!(#[derive(Ord, PartialOrd)])
183 };
184185let vis = &input.vis;
186187let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}");
188189// 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
200quote!(
201#[repr(transparent)]
202 #[derive(Copy, Clone, PartialEq, Eq)]
203#maybe_ord_derives
204#[doc = #doc]
205#vis struct #ule_name(u8);
206207unsafe impl zerovec::ule::ULE for #ule_name {
208#[inline]
209fn validate_bytes(bytes: &[u8]) -> Result<(), zerovec::ule::UleError> {
210for byte in bytes {
211if *byte < #min || *byte > #max {
212return Err(zerovec::ule::UleError::parse::<Self>())
213 }
214 }
215Ok(())
216 }
217 }
218219impl zerovec::ule::AsULE for #name {
220type ULE = #ule_name;
221222fn to_unaligned(self) -> Self::ULE {
223// safety: the enum is repr(u8) and can be cast to a u8
224unsafe {
225 ::core::mem::transmute(self)
226 }
227 }
228229fn 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.
232unsafe {
233 ::core::mem::transmute(other)
234 }
235 }
236 }
237238impl #name {
239/// Attempt to construct the value from its corresponding integer,
240 /// returning `None` if not possible
241pub(crate) fn new_from_u8(value: u8) -> Option<Self> {
242if value <= #max {
243unsafe {
244Some(::core::mem::transmute(value))
245 }
246 } else {
247None
248}
249 }
250 }
251 )
252}
253254fn get_expr_int(e: &Expr) -> Option<u64> {
255if let Ok(Lit::Int(ref i)) = syn::parse2(quote!(#e)) {
256return i.base10_parse().ok();
257 }
258259None
260}
261262fn make_ule_struct_impl(
263 name: &Ident,
264 ule_name: &Ident,
265 input: &DeriveInput,
266 struc: &DataStruct,
267 attrs: &ZeroVecAttrs,
268) -> TokenStream2 {
269if struc.fields.iter().next().is_none() {
270return Error::new(
271 input.span(),
272"#[make_ule] must be applied to a non-empty struct",
273 )
274 .to_compile_error();
275 }
276let sized_fields = FieldInfo::make_list(struc.fields.iter());
277let field_inits = crate::ule::make_ule_fields(&sized_fields);
278let field_inits = utils::wrap_field_inits(&field_inits, &struc.fields);
279280let semi = utils::semi_for(&struc.fields);
281let repr_attr = utils::repr_for(&struc.fields);
282let vis = &input.vis;
283284let doc = format!("[`ULE`](zerovec::ule::ULE) type for [`{name}`]");
285286let 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 );
294let derived = crate::ule::derive_impl(&ule_struct);
295296let mut as_ule_conversions = vec![];
297let mut from_ule_conversions = vec![];
298299for (i, field) in struc.fields.iter().enumerate() {
300let ty = &field.ty;
301let i = syn::Index::from(i);
302if 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(
306quote!(#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 }
314315let as_ule_conversions = utils::wrap_field_inits(&as_ule_conversions, &struc.fields);
316let from_ule_conversions = utils::wrap_field_inits(&from_ule_conversions, &struc.fields);
317let asule_impl = quote!(
318impl zerovec::ule::AsULE for #name {
319type ULE = #ule_name;
320fn to_unaligned(self) -> Self::ULE {
321 #ule_name #as_ule_conversions
322 }
323fn from_unaligned(unaligned: Self::ULE) -> Self {
324Self #from_ule_conversions
325 }
326 }
327 );
328329let maybe_ord_impls = if attrs.skip_ord {
330quote!()
331 } else {
332quote!(
333impl core::cmp::PartialOrd for #ule_name {
334fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
335Some(self.cmp(other))
336 }
337 }
338339impl core::cmp::Ord for #ule_name {
340fn cmp(&self, other: &Self) -> core::cmp::Ordering {
341let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self);
342let other = <#name as zerovec::ule::AsULE>::from_unaligned(*other);
343 <#name as core::cmp::Ord>::cmp(&this, &other)
344 }
345 }
346 )
347 };
348349let maybe_hash = if attrs.hash {
350quote!(
351#[allow(clippy::derive_hash_xor_eq)]
352impl core::hash::Hash for #ule_name {
353fn 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 {
359quote!()
360 };
361362quote!(
363 #asule_impl
364365 #ule_struct
366367 #derived
368369 #maybe_ord_impls
370371 #maybe_hash
372 )
373}