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 crate::utils::{self, FieldInfo};
6use proc_macro2::Span;
7use proc_macro2::TokenStream as TokenStream2;
8use quote::quote;
9use syn::spanned::Spanned;
10use syn::{Data, DeriveInput, Error, Ident};
11
12/// Implementation for derive(VarULE). `custom_varule_validator` validates the last field bytes `last_field_bytes`
13/// if specified, if not, the VarULE implementation will be used.
14pub fn derive_impl(
15 input: &DeriveInput,
16 custom_varule_validator: Option<TokenStream2>,
17) -> TokenStream2 {
18 if !utils::has_valid_repr(&input.attrs, |r| r == "packed" || r == "transparent") {
19 return Error::new(
20 input.span(),
21 "derive(VarULE) must be applied to a #[repr(packed)] or #[repr(transparent)] type",
22 )
23 .to_compile_error();
24 }
25 if input.generics.type_params().next().is_some()
26 || input.generics.lifetimes().next().is_some()
27 || input.generics.const_params().next().is_some()
28 {
29 return Error::new(
30 input.generics.span(),
31 "derive(VarULE) must be applied to a struct without any generics",
32 )
33 .to_compile_error();
34 }
35 let struc = if let Data::Struct(ref s) = input.data {
36 if s.fields.iter().next().is_none() {
37 return Error::new(
38 input.span(),
39 "derive(VarULE) must be applied to a non-empty struct",
40 )
41 .to_compile_error();
42 }
43 s
44 } else {
45 return Error::new(input.span(), "derive(VarULE) must be applied to a struct")
46 .to_compile_error();
47 };
48
49 let n_fields = struc.fields.len();
50
51 let ule_fields = FieldInfo::make_list(struc.fields.iter().take(n_fields - 1));
52
53 let sizes = ule_fields.iter().map(|f| {
54 let ty = &f.field.ty;
55 quote!(::core::mem::size_of::<#ty>())
56 });
57 let (validators, remaining_offset) = if n_fields > 1 {
58 // generate ULE validators
59 crate::ule::generate_ule_validators(&ule_fields)
60 } else {
61 // no ULE subfields
62 (
63 quote!(
64 const ZERO: usize = 0;
65 ),
66 Ident::new("ZERO", Span::call_site()),
67 )
68 };
69
70 let unsized_field = &struc
71 .fields
72 .iter()
73 .next_back()
74 .expect("Already verified that struct is not empty")
75 .ty;
76
77 let name = &input.ident;
78 let ule_size = Ident::new(
79 &format!("__IMPL_VarULE_FOR_{name}_ULE_SIZE"),
80 Span::call_site(),
81 );
82
83 let last_field_validator = if let Some(custom_varule_validator) = custom_varule_validator {
84 custom_varule_validator
85 } else {
86 quote!(<#unsized_field as zerovec::ule::VarULE>::validate_byte_slice(last_field_bytes)?;)
87 };
88
89 // Safety (based on the safety checklist on the ULE trait):
90 // 1. #name does not include any uninitialized or padding bytes
91 // (achieved by enforcing #[repr(transparent)] or #[repr(packed)] on a struct of only ULE types)
92 // 2. #name is aligned to 1 byte.
93 // (achieved by enforcing #[repr(transparent)] or #[repr(packed)] on a struct of only ULE types)
94 // 3. The impl of `validate_byte_slice()` returns an error if any byte is not valid.
95 // 4. The impl of `validate_byte_slice()` returns an error if the slice cannot be used in its entirety
96 // 5. The impl of `from_byte_slice_unchecked()` returns a reference to the same data.
97 // 6. The other VarULE methods use the default impl
98 // 7. [This impl does not enforce the non-safety equality constraint, it is up to the user to do so, ideally via a custom derive]
99 quote! {
100 // The size of the ULE section of this type
101 const #ule_size: usize = 0 #(+ #sizes)*;
102 unsafe impl zerovec::ule::VarULE for #name {
103 #[inline]
104 fn validate_byte_slice(bytes: &[u8]) -> Result<(), zerovec::ZeroVecError> {
105
106 if bytes.len() < #ule_size {
107 return Err(zerovec::ZeroVecError::parse::<Self>());
108 }
109 #validators
110 debug_assert_eq!(#remaining_offset, #ule_size);
111 #[allow(clippy::indexing_slicing)] // TODO explain
112 let last_field_bytes = &bytes[#remaining_offset..];
113 #last_field_validator
114 Ok(())
115 }
116 #[inline]
117 unsafe fn from_byte_slice_unchecked(bytes: &[u8]) -> &Self {
118 // just the unsized part
119 #[allow(clippy::indexing_slicing)] // TODO explain
120 let unsized_bytes = &bytes[#ule_size..];
121 let unsized_ref = <#unsized_field as zerovec::ule::VarULE>::from_byte_slice_unchecked(unsized_bytes);
122 // We should use the pointer metadata APIs here when they are stable: https://github.com/rust-lang/rust/issues/81513
123 // For now we rely on all DST metadata being a usize to extract it via a fake slice pointer
124 let (_ptr, metadata): (usize, usize) = ::core::mem::transmute(unsized_ref);
125 let entire_struct_as_slice: *const [u8] = ::core::slice::from_raw_parts(bytes.as_ptr(), metadata);
126 &*(entire_struct_as_slice as *const Self)
127 }
128 }
129 }
130}
131