Revision control

Copy as Markdown

Other Tools

/* 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()
}