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 quote::quote;
6
7use proc_macro2::Span;
8use proc_macro2::TokenStream as TokenStream2;
9use syn::parse::{Parse, ParseStream};
10use syn::punctuated::Punctuated;
11use syn::spanned::Spanned;
12use syn::{Attribute, Error, Field, Fields, Ident, Index, Result, Token};
13
14// Check that there are repr attributes satisfying the given predicate
15pub fn has_valid_repr(attrs: &[Attribute], predicate: impl Fn(&Ident) -> bool + Copy) -> bool {
16 attrs.iter().filter(|a: &&Attribute| a.path().is_ident("repr")).any(|a: &Attribute| {
17 aOption<()>.parse_args::<IdentListAttribute>()
18 .ok()
19 .and_then(|s: IdentListAttribute| s.idents.iter().find(|s: &&Ident| predicate(s)).map(|_| ()))
20 .is_some()
21 })
22}
23
24// An attribute that is a list of idents
25struct IdentListAttribute {
26 idents: Punctuated<Ident, Token![,]>,
27}
28
29impl Parse for IdentListAttribute {
30 fn parse(input: ParseStream) -> Result<Self> {
31 Ok(IdentListAttribute {
32 idents: input.parse_terminated(parser:Ident::parse, separator:Token![,])?,
33 })
34 }
35}
36
37/// Given a set of entries for struct field definitions to go inside a `struct {}` definition,
38/// wrap in a () or {} based on the type of field
39pub fn wrap_field_inits(streams: &[TokenStream2], fields: &Fields) -> TokenStream2 {
40 match *fields {
41 Fields::Named(_) => quote!( { #(#streams),* } ),
42 Fields::Unnamed(_) => quote!( ( #(#streams),* ) ),
43 Fields::Unit => {
44 unreachable!("#[make_(var)ule] should have already checked that there are fields")
45 }
46 }
47}
48
49/// Return a semicolon token if necessary after the struct definition
50pub fn semi_for(f: &Fields) -> TokenStream2 {
51 if let Fields::Unnamed(..) = *f {
52 quote!(;)
53 } else {
54 quote!()
55 }
56}
57
58/// Returns the repr attribute to be applied to the resultant ULE or VarULE type
59pub fn repr_for(f: &Fields) -> TokenStream2 {
60 if f.len() == 1 {
61 quote!(transparent)
62 } else {
63 quote!(packed)
64 }
65}
66
67fn suffixed_ident(name: &str, suffix: usize, s: Span) -> Ident {
68 Ident::new(&format!("{name}_{suffix}"), span:s)
69}
70
71/// Given an iterator over ULE or AsULE struct fields, returns code that calculates field sizes and generates a line
72/// of code per field based on the per_field_code function (whose parameters are the field, the identifier of the const
73/// for the previous offset, the identifier for the const for the next offset, and the field index)
74pub(crate) fn generate_per_field_offsets<'a>(
75 fields: &[FieldInfo<'a>],
76 // Whether the fields are ULE types or AsULE (and need conversion)
77 fields_are_asule: bool,
78 // (field, prev_offset_ident, size_ident)
79 mut per_field_code: impl FnMut(&FieldInfo<'a>, &Ident, &Ident) -> TokenStream2, /* (code, remaining_offset) */
80) -> (TokenStream2, syn::Ident) {
81 let mut prev_offset_ident = Ident::new("ZERO", Span::call_site());
82 let mut code = quote!(
83 const ZERO: usize = 0;
84 );
85
86 for (i, field_info) in fields.iter().enumerate() {
87 let field = &field_info.field;
88 let ty = &field.ty;
89 let ty = if fields_are_asule {
90 quote!(<#ty as zerovec::ule::AsULE>::ULE)
91 } else {
92 quote!(#ty)
93 };
94 let new_offset_ident = suffixed_ident("OFFSET", i, field.span());
95 let size_ident = suffixed_ident("SIZE", i, field.span());
96 let pf_code = per_field_code(field_info, &prev_offset_ident, &size_ident);
97 code = quote! {
98 #code;
99 const #size_ident: usize = ::core::mem::size_of::<#ty>();
100 const #new_offset_ident: usize = #prev_offset_ident + #size_ident;
101 #pf_code;
102 };
103
104 prev_offset_ident = new_offset_ident;
105 }
106
107 (code, prev_offset_ident)
108}
109
110#[derive(Clone, Debug)]
111pub(crate) struct FieldInfo<'a> {
112 pub accessor: TokenStream2,
113 pub field: &'a Field,
114 pub index: usize,
115}
116
117impl<'a> FieldInfo<'a> {
118 pub fn make_list(iter: impl Iterator<Item = &'a Field>) -> Vec<Self> {
119 iter.enumerate()
120 .map(|(i, field)| Self::new_for_field(field, i))
121 .collect()
122 }
123
124 pub fn new_for_field(f: &'a Field, index: usize) -> Self {
125 if let Some(ref i) = f.ident {
126 FieldInfo {
127 accessor: quote!(#i),
128 field: f,
129 index,
130 }
131 } else {
132 let idx = Index::from(index);
133 FieldInfo {
134 accessor: quote!(#idx),
135 field: f,
136 index,
137 }
138 }
139 }
140
141 /// Get the code for setting this field in struct decl/brace syntax
142 ///
143 /// Use self.accessor for dot-notation accesses
144 pub fn setter(&self) -> TokenStream2 {
145 if let Some(ref i) = self.field.ident {
146 quote!(#i: )
147 } else {
148 quote!()
149 }
150 }
151}
152
153/// Extracts all `zerovec::name(..)` attribute
154pub fn extract_parenthetical_zerovec_attrs(
155 attrs: &mut Vec<Attribute>,
156 name: &str,
157) -> Result<Vec<Ident>> {
158 let mut ret = vec![];
159 let mut error = None;
160 attrs.retain(|a| {
161 // skip the "zerovec" part
162 let second_segment = a.path().segments.iter().nth(1);
163
164 if let Some(second) = second_segment {
165 if second.ident == name {
166 let list = match a.parse_args::<IdentListAttribute>() {
167 Ok(l) => l,
168 Err(_) => {
169 error = Some(Error::new(
170 a.span(),
171 format!("#[zerovec::{name}(..)] takes in a comma separated list of identifiers"),
172 ));
173 return false;
174 }
175 };
176 ret.extend(list.idents.iter().cloned());
177 return false;
178 }
179 }
180
181 true
182 });
183
184 if let Some(error) = error {
185 return Err(error);
186 }
187 Ok(ret)
188}
189
190/// Removes all attributes with `zerovec` in the name and places them in a separate vector
191pub fn extract_zerovec_attributes(attrs: &mut Vec<Attribute>) -> Vec<Attribute> {
192 let mut ret: Vec = vec![];
193 attrs.retain(|a: &Attribute| {
194 if a.path().segments.len() == 2 && a.path().segments[0].ident == "zerovec" {
195 ret.push(a.clone());
196 return false;
197 }
198 true
199 });
200 ret
201}
202
203/// Extract attributes from field, and return them
204///
205/// Only current field attribute is `zerovec::varule(VarUleType)`
206pub fn extract_field_attributes(attrs: &mut Vec<Attribute>) -> Result<Option<Ident>> {
207 let mut zerovec_attrs: Vec = extract_zerovec_attributes(attrs);
208 let varule: Vec = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, name:"varule")?;
209
210 if varule.len() > 1 {
211 return Err(Error::new(
212 varule[1].span(),
213 message:"Found multiple #[zerovec::varule()] on one field",
214 ));
215 }
216
217 if !zerovec_attrs.is_empty() {
218 return Err(Error::new(
219 zerovec_attrs[1].span(),
220 message:"Found unusable #[zerovec::] attrs on field, only #[zerovec::varule()] supported",
221 ));
222 }
223
224 Ok(varule.get(index:0).cloned())
225}
226
227#[derive(Default, Copy, Clone)]
228pub struct ZeroVecAttrs {
229 pub skip_kv: bool,
230 pub skip_ord: bool,
231 pub serialize: bool,
232 pub deserialize: bool,
233 pub debug: bool,
234 pub hash: bool,
235}
236
237/// Removes all known zerovec:: attributes from struct attrs and validates them
238pub fn extract_attributes_common(
239 attrs: &mut Vec<Attribute>,
240 span: Span,
241 is_var: bool,
242) -> Result<ZeroVecAttrs> {
243 let mut zerovec_attrs = extract_zerovec_attributes(attrs);
244
245 let derive = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "derive")?;
246 let skip = extract_parenthetical_zerovec_attrs(&mut zerovec_attrs, "skip_derive")?;
247
248 let name = if is_var { "make_varule" } else { "make_ule" };
249
250 if let Some(attr) = zerovec_attrs.get(0) {
251 return Err(Error::new(
252 attr.span(),
253 format!("Found unknown or duplicate attribute for #[{name}]"),
254 ));
255 }
256
257 let mut attrs = ZeroVecAttrs::default();
258
259 for ident in derive {
260 if ident == "Serialize" {
261 attrs.serialize = true;
262 } else if ident == "Deserialize" {
263 attrs.deserialize = true;
264 } else if ident == "Debug" {
265 attrs.debug = true;
266 } else if ident == "Hash" {
267 attrs.hash = true;
268 } else {
269 return Err(Error::new(
270 ident.span(),
271 format!(
272 "Found unknown derive attribute for #[{name}]: #[zerovec::derive({ident})]"
273 ),
274 ));
275 }
276 }
277
278 for ident in skip {
279 if ident == "ZeroMapKV" {
280 attrs.skip_kv = true;
281 } else if ident == "Ord" {
282 attrs.skip_ord = true;
283 } else {
284 return Err(Error::new(
285 ident.span(),
286 format!("Found unknown derive attribute for #[{name}]: #[zerovec::skip_derive({ident})]"),
287 ));
288 }
289 }
290
291 if (attrs.serialize || attrs.deserialize) && !is_var {
292 return Err(Error::new(
293 span,
294 "#[make_ule] does not support #[zerovec::derive(Serialize, Deserialize)]",
295 ));
296 }
297
298 Ok(attrs)
299}
300