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