zerovec_derive/
make_ule.rs1use 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 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 let mut min = None;
101 let mut max = None;
103 let mut not_found = HashSet::new();
105
106 for (i, variant) in enu.variants.iter().enumerate() {
107 if !matches!(variant.fields, Fields::Unit) {
108 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 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 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 unsafe {
225 ::core::mem::transmute(self)
226 }
227 }
228
229 fn from_unaligned(other: Self::ULE) -> Self {
230 unsafe {
233 ::core::mem::transmute(other)
234 }
235 }
236 }
237
238 impl #name {
239 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 #[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}