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 quote::quote; |
6 | |
7 | use proc_macro2::Span; |
8 | use proc_macro2::TokenStream as TokenStream2; |
9 | use syn::parse::{Parse, ParseStream}; |
10 | use syn::punctuated::Punctuated; |
11 | use syn::spanned::Spanned; |
12 | use syn::{Attribute, Error, Field, Fields, Ident, Index, Result, Token}; |
13 | |
14 | // Check that there are repr attributes satisfying the given predicate |
15 | pub 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 |
25 | struct IdentListAttribute { |
26 | idents: Punctuated<Ident, Token![,]>, |
27 | } |
28 | |
29 | impl 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 |
39 | pub 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 |
50 | pub 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 |
59 | pub fn repr_for(f: &Fields) -> TokenStream2 { |
60 | if f.len() == 1 { |
61 | quote!(transparent) |
62 | } else { |
63 | quote!(packed) |
64 | } |
65 | } |
66 | |
67 | fn 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) |
74 | pub(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)] |
111 | pub(crate) struct FieldInfo<'a> { |
112 | pub accessor: TokenStream2, |
113 | pub field: &'a Field, |
114 | pub index: usize, |
115 | } |
116 | |
117 | impl<'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 |
154 | pub 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 |
191 | pub 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)` |
206 | pub 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)] |
228 | pub 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 |
238 | pub 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 | |