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 | |
13 | 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 | |
78 | 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::has_valid_repr(&input.attrs, |r| r == "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 | // the next discriminant expected |
95 | let mut next = 0; |
96 | // Discriminants that have not been found in series (we might find them later) |
97 | let mut not_found = HashSet::new(); |
98 | |
99 | for (i, variant) in enu.variants.iter().enumerate() { |
100 | if !matches!(variant.fields, Fields::Unit) { |
101 | // This can be supported in the future, see zerovec/design_doc.md |
102 | return Error::new( |
103 | variant.span(), |
104 | "#[make_ule] can only be applied to enums with dataless variants" , |
105 | ) |
106 | .to_compile_error(); |
107 | } |
108 | |
109 | if let Some((_, ref discr)) = variant.discriminant { |
110 | if let Some(n) = get_expr_int(discr) { |
111 | if n >= next { |
112 | for missing in next..n { |
113 | not_found.insert(missing); |
114 | } |
115 | next = n + 1; |
116 | } |
117 | |
118 | not_found.remove(&n); |
119 | |
120 | // We require explicit discriminants so that it is clear that reordering |
121 | // fields would be a breaking change. Furthermore, using explicit discriminants helps ensure that |
122 | // platform-specific C ABI choices do not matter. |
123 | // We could potentially add in explicit discriminants on the user's behalf in the future, or support |
124 | // more complicated sets of explicit discriminant values. |
125 | if n != i as u64 {} |
126 | } else { |
127 | return Error::new( |
128 | discr.span(), |
129 | "#[make_ule] must be applied to enums with explicit integer discriminants" , |
130 | ) |
131 | .to_compile_error(); |
132 | } |
133 | } else { |
134 | return Error::new( |
135 | variant.span(), |
136 | "#[make_ule] must be applied to enums with explicit discriminants" , |
137 | ) |
138 | .to_compile_error(); |
139 | } |
140 | } |
141 | |
142 | let not_found = not_found.iter().collect::<Vec<_>>(); |
143 | |
144 | if !not_found.is_empty() { |
145 | return Error::new(input.span(), format!("#[make_ule] must be applied to enums with discriminants \ |
146 | filling the range from 0 to a maximum; could not find {not_found:?}" )) |
147 | .to_compile_error(); |
148 | } |
149 | |
150 | let max = next as u8; |
151 | |
152 | let maybe_ord_derives = if attrs.skip_ord { |
153 | quote!() |
154 | } else { |
155 | quote!(#[derive(Ord, PartialOrd)]) |
156 | }; |
157 | |
158 | let vis = &input.vis; |
159 | |
160 | let doc = format!("[`ULE`](zerovec::ule::ULE) type for {name}" ); |
161 | |
162 | // Safety (based on the safety checklist on the ULE trait): |
163 | // 1. ULE type does not include any uninitialized or padding bytes. |
164 | // (achieved by `#[repr(transparent)]` on a type that satisfies this invariant |
165 | // 2. ULE type is aligned to 1 byte. |
166 | // (achieved by `#[repr(transparent)]` on a type that satisfies this invariant) |
167 | // 3. The impl of validate_byte_slice() returns an error if any byte is not valid. |
168 | // (Guarantees that the byte is in range of the corresponding enum.) |
169 | // 4. The impl of validate_byte_slice() returns an error if there are extra bytes. |
170 | // (This does not happen since we are backed by 1 byte.) |
171 | // 5. The other ULE methods use the default impl. |
172 | // 6. ULE type byte equality is semantic equality |
173 | quote!( |
174 | #[repr(transparent)] |
175 | #[derive(Copy, Clone, PartialEq, Eq)] |
176 | #maybe_ord_derives |
177 | #[doc = #doc] |
178 | #vis struct #ule_name(u8); |
179 | |
180 | unsafe impl zerovec::ule::ULE for #ule_name { |
181 | #[inline] |
182 | fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> { |
183 | for byte in bytes { |
184 | if *byte >= #max { |
185 | return Err(zerovec::ZeroVecError::parse::<Self>()) |
186 | } |
187 | } |
188 | Ok(()) |
189 | } |
190 | } |
191 | |
192 | impl zerovec::ule::AsULE for #name { |
193 | type ULE = #ule_name; |
194 | |
195 | fn to_unaligned(self) -> Self::ULE { |
196 | // safety: the enum is repr(u8) and can be cast to a u8 |
197 | unsafe { |
198 | ::core::mem::transmute(self) |
199 | } |
200 | } |
201 | |
202 | fn from_unaligned(other: Self::ULE) -> Self { |
203 | // safety: the enum is repr(u8) and can be cast from a u8, |
204 | // and `#ule_name` guarantees a valid value for this enum. |
205 | unsafe { |
206 | ::core::mem::transmute(other) |
207 | } |
208 | } |
209 | } |
210 | |
211 | impl #name { |
212 | /// Attempt to construct the value from its corresponding integer, |
213 | /// returning `None` if not possible |
214 | pub(crate) fn new_from_u8(value: u8) -> Option<Self> { |
215 | if value <= #max { |
216 | unsafe { |
217 | Some(::core::mem::transmute(value)) |
218 | } |
219 | } else { |
220 | None |
221 | } |
222 | } |
223 | } |
224 | ) |
225 | } |
226 | |
227 | fn get_expr_int(e: &Expr) -> Option<u64> { |
228 | if let Ok(Lit::Int(ref i: &LitInt)) = syn::parse2(tokens:quote!(#e)) { |
229 | return i.base10_parse().ok(); |
230 | } |
231 | |
232 | None |
233 | } |
234 | |
235 | fn make_ule_struct_impl( |
236 | name: &Ident, |
237 | ule_name: &Ident, |
238 | input: &DeriveInput, |
239 | struc: &DataStruct, |
240 | attrs: ZeroVecAttrs, |
241 | ) -> TokenStream2 { |
242 | if struc.fields.iter().next().is_none() { |
243 | return Error::new( |
244 | input.span(), |
245 | "#[make_ule] must be applied to a non-empty struct" , |
246 | ) |
247 | .to_compile_error(); |
248 | } |
249 | let sized_fields = FieldInfo::make_list(struc.fields.iter()); |
250 | let field_inits = crate::ule::make_ule_fields(&sized_fields); |
251 | let field_inits = utils::wrap_field_inits(&field_inits, &struc.fields); |
252 | |
253 | let semi = utils::semi_for(&struc.fields); |
254 | let repr_attr = utils::repr_for(&struc.fields); |
255 | let vis = &input.vis; |
256 | |
257 | let doc = format!("[`ULE`](zerovec::ule::ULE) type for [` {name}`]" ); |
258 | |
259 | let ule_struct: DeriveInput = parse_quote!( |
260 | #[repr(#repr_attr)] |
261 | #[derive(Copy, Clone, PartialEq, Eq)] |
262 | #[doc = #doc] |
263 | // We suppress the `missing_docs` lint for the fields of the struct. |
264 | #[allow(missing_docs)] |
265 | #vis struct #ule_name #field_inits #semi |
266 | ); |
267 | let derived = crate::ule::derive_impl(&ule_struct); |
268 | |
269 | let mut as_ule_conversions = vec![]; |
270 | let mut from_ule_conversions = vec![]; |
271 | |
272 | for (i, field) in struc.fields.iter().enumerate() { |
273 | let ty = &field.ty; |
274 | let i = syn::Index::from(i); |
275 | if let Some(ref ident) = field.ident { |
276 | as_ule_conversions |
277 | .push(quote!(#ident: <#ty as zerovec::ule::AsULE>::to_unaligned(self.#ident))); |
278 | from_ule_conversions.push( |
279 | quote!(#ident: <#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#ident)), |
280 | ); |
281 | } else { |
282 | as_ule_conversions.push(quote!(<#ty as zerovec::ule::AsULE>::to_unaligned(self.#i))); |
283 | from_ule_conversions |
284 | .push(quote!(<#ty as zerovec::ule::AsULE>::from_unaligned(unaligned.#i))); |
285 | }; |
286 | } |
287 | |
288 | let as_ule_conversions = utils::wrap_field_inits(&as_ule_conversions, &struc.fields); |
289 | let from_ule_conversions = utils::wrap_field_inits(&from_ule_conversions, &struc.fields); |
290 | let asule_impl = quote!( |
291 | impl zerovec::ule::AsULE for #name { |
292 | type ULE = #ule_name; |
293 | fn to_unaligned(self) -> Self::ULE { |
294 | #ule_name #as_ule_conversions |
295 | } |
296 | fn from_unaligned(unaligned: Self::ULE) -> Self { |
297 | Self #from_ule_conversions |
298 | } |
299 | } |
300 | ); |
301 | |
302 | let maybe_ord_impls = if attrs.skip_ord { |
303 | quote!() |
304 | } else { |
305 | quote!( |
306 | impl core::cmp::PartialOrd for #ule_name { |
307 | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { |
308 | let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self); |
309 | let other = <#name as zerovec::ule::AsULE>::from_unaligned(*other); |
310 | <#name as core::cmp::PartialOrd>::partial_cmp(&this, &other) |
311 | } |
312 | } |
313 | |
314 | impl core::cmp::Ord for #ule_name { |
315 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { |
316 | let this = <#name as zerovec::ule::AsULE>::from_unaligned(*self); |
317 | let other = <#name as zerovec::ule::AsULE>::from_unaligned(*other); |
318 | <#name as core::cmp::Ord>::cmp(&this, &other) |
319 | } |
320 | } |
321 | ) |
322 | }; |
323 | |
324 | let maybe_hash = if attrs.hash { |
325 | quote!( |
326 | #[allow(clippy::derive_hash_xor_eq)] |
327 | impl core::hash::Hash for #ule_name { |
328 | fn hash<H>(&self, state: &mut H) where H: core::hash::Hasher { |
329 | state.write(<#ule_name as zerovec::ule::ULE>::as_byte_slice(&[*self])); |
330 | } |
331 | } |
332 | ) |
333 | } else { |
334 | quote!() |
335 | }; |
336 | |
337 | quote!( |
338 | #asule_impl |
339 | |
340 | #ule_struct |
341 | |
342 | #derived |
343 | |
344 | #maybe_ord_impls |
345 | |
346 | #maybe_hash |
347 | ) |
348 | } |
349 | |