| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| //! Custom derive for uniffi_meta::Checksum |
| |
| use proc_macro::TokenStream; |
| use quote::{format_ident, quote}; |
| use syn::{ |
| parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, Fields, Index, Lit, Meta, |
| }; |
| |
| fn has_ignore_attribute(attrs: &[Attribute]) -> bool { |
| attrs.iter().any(|attr| { |
| if attr.path().is_ident("checksum_ignore") { |
| if let Meta::List(_) | Meta::NameValue(_) = &attr.meta { |
| panic!("#[checksum_ignore] doesn't accept extra information"); |
| } |
| true |
| } else { |
| false |
| } |
| }) |
| } |
| |
| #[proc_macro_derive(Checksum, attributes(checksum_ignore))] |
| pub fn checksum_derive(input: TokenStream) -> TokenStream { |
| let input: DeriveInput = parse_macro_input!(input); |
| |
| let name = input.ident; |
| |
| let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); |
| |
| let code = match input.data { |
| Data::Enum(enum_) |
| if enum_.variants.len() == 1 |
| && enum_ |
| .variants |
| .iter() |
| .all(|variant| matches!(variant.fields, Fields::Unit)) => |
| { |
| quote!() |
| } |
| Data::Enum(enum_) => { |
| let mut next_discriminant = 0u64; |
| let match_inner = enum_.variants.iter().map(|variant| { |
| let ident = &variant.ident; |
| if has_ignore_attribute(&variant.attrs) { |
| panic!("#[checksum_ignore] is not supported in enums"); |
| } |
| match &variant.discriminant { |
| Some((_, Expr::Lit(ExprLit { lit: Lit::Int(value), .. }))) => { |
| next_discriminant = value.base10_parse::<u64>().unwrap(); |
| } |
| Some(_) => { |
| panic!("#[derive(Checksum)] doesn't support non-numeric explicit discriminants in enums"); |
| } |
| None => {} |
| } |
| let discriminant = quote! { state.write(&#next_discriminant.to_le_bytes()) }; |
| next_discriminant += 1; |
| match &variant.fields { |
| Fields::Unnamed(fields) => { |
| let field_idents = fields |
| .unnamed |
| .iter() |
| .enumerate() |
| .map(|(num, _)| format_ident!("__self_{}", num)); |
| let field_stmts = field_idents |
| .clone() |
| .map(|ident| quote! { Checksum::checksum(#ident, state); }); |
| quote! { |
| Self::#ident(#(#field_idents,)*) => { |
| #discriminant; |
| #(#field_stmts)* |
| } |
| } |
| } |
| Fields::Named(fields) => { |
| let field_idents = fields |
| .named |
| .iter() |
| .map(|field| field.ident.as_ref().unwrap()); |
| let field_stmts = fields.named.iter() |
| .filter(|field| !has_ignore_attribute(&field.attrs)) |
| .map(|field| { |
| let ident = field.ident.as_ref().unwrap(); |
| quote! { Checksum::checksum(#ident, state); } |
| }); |
| quote! { |
| Self::#ident { #(#field_idents,)* } => { |
| #discriminant; |
| #(#field_stmts)* |
| } |
| } |
| } |
| Fields::Unit => quote! { Self::#ident => #discriminant, }, |
| } |
| }); |
| quote! { |
| match self { |
| #(#match_inner)* |
| } |
| } |
| } |
| Data::Struct(struct_) => { |
| let stmts = struct_ |
| .fields |
| .iter() |
| .enumerate() |
| .filter(|&(_num, field)| (!has_ignore_attribute(&field.attrs))) |
| .map(|(num, field)| match field.ident.as_ref() { |
| Some(ident) => quote! { Checksum::checksum(&self.#ident, state); }, |
| None => { |
| let i = Index::from(num); |
| quote! { Checksum::checksum(&self.#i, state); } |
| } |
| }); |
| quote! { |
| #(#stmts)* |
| } |
| } |
| Data::Union(_) => { |
| panic!("#[derive(Checksum)] is not supported for unions"); |
| } |
| }; |
| |
| quote! { |
| #[automatically_derived] |
| impl #impl_generics Checksum for #name #ty_generics #where_clause { |
| fn checksum<__H: ::core::hash::Hasher>(&self, state: &mut __H) { |
| #code |
| } |
| } |
| } |
| .into() |
| } |