1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 //! Custom derive for uniffi_meta::Checksum
6 
7 use proc_macro::TokenStream;
8 use quote::{format_ident, quote};
9 use syn::{
10     parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, Fields, Index, Lit, Meta,
11 };
12 
has_ignore_attribute(attrs: &[Attribute]) -> bool13 fn has_ignore_attribute(attrs: &[Attribute]) -> bool {
14     attrs.iter().any(|attr| {
15         if attr.path().is_ident("checksum_ignore") {
16             if let Meta::List(_) | Meta::NameValue(_) = &attr.meta {
17                 panic!("#[checksum_ignore] doesn't accept extra information");
18             }
19             true
20         } else {
21             false
22         }
23     })
24 }
25 
26 #[proc_macro_derive(Checksum, attributes(checksum_ignore))]
checksum_derive(input: TokenStream) -> TokenStream27 pub fn checksum_derive(input: TokenStream) -> TokenStream {
28     let input: DeriveInput = parse_macro_input!(input);
29 
30     let name = input.ident;
31 
32     let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
33 
34     let code = match input.data {
35         Data::Enum(enum_)
36             if enum_.variants.len() == 1
37                 && enum_
38                     .variants
39                     .iter()
40                     .all(|variant| matches!(variant.fields, Fields::Unit)) =>
41         {
42             quote!()
43         }
44         Data::Enum(enum_) => {
45             let mut next_discriminant = 0u64;
46             let match_inner = enum_.variants.iter().map(|variant| {
47                 let ident = &variant.ident;
48                 if has_ignore_attribute(&variant.attrs) {
49                     panic!("#[checksum_ignore] is not supported in enums");
50                 }
51                 match &variant.discriminant {
52                     Some((_, Expr::Lit(ExprLit { lit: Lit::Int(value), .. }))) => {
53                         next_discriminant = value.base10_parse::<u64>().unwrap();
54                     }
55                     Some(_) => {
56                         panic!("#[derive(Checksum)] doesn't support non-numeric explicit discriminants in enums");
57                     }
58                     None => {}
59                 }
60                 let discriminant = quote! { state.write(&#next_discriminant.to_le_bytes()) };
61                 next_discriminant += 1;
62                 match &variant.fields {
63                     Fields::Unnamed(fields) => {
64                         let field_idents = fields
65                             .unnamed
66                             .iter()
67                             .enumerate()
68                             .map(|(num, _)| format_ident!("__self_{}", num));
69                         let field_stmts = field_idents
70                             .clone()
71                             .map(|ident| quote! { Checksum::checksum(#ident, state); });
72                         quote! {
73                             Self::#ident(#(#field_idents,)*) => {
74                                 #discriminant;
75                                 #(#field_stmts)*
76                             }
77                         }
78                     }
79                     Fields::Named(fields) => {
80                         let field_idents = fields
81                             .named
82                             .iter()
83                             .map(|field| field.ident.as_ref().unwrap());
84                         let field_stmts = fields.named.iter()
85                             .filter(|field| !has_ignore_attribute(&field.attrs))
86                             .map(|field| {
87                                     let ident = field.ident.as_ref().unwrap();
88                                     quote! { Checksum::checksum(#ident, state); }
89                             });
90                         quote! {
91                             Self::#ident { #(#field_idents,)* } => {
92                                 #discriminant;
93                                 #(#field_stmts)*
94                             }
95                         }
96                     }
97                     Fields::Unit => quote! { Self::#ident => #discriminant, },
98                 }
99             });
100             quote! {
101                 match self {
102                     #(#match_inner)*
103                 }
104             }
105         }
106         Data::Struct(struct_) => {
107             let stmts = struct_
108                 .fields
109                 .iter()
110                 .enumerate()
111                 .filter_map(|(num, field)| {
112                     (!has_ignore_attribute(&field.attrs)).then(|| match field.ident.as_ref() {
113                         Some(ident) => quote! { Checksum::checksum(&self.#ident, state); },
114                         None => {
115                             let i = Index::from(num);
116                             quote! { Checksum::checksum(&self.#i, state); }
117                         }
118                     })
119                 });
120             quote! {
121                 #(#stmts)*
122             }
123         }
124         Data::Union(_) => {
125             panic!("#[derive(Checksum)] is not supported for unions");
126         }
127     };
128 
129     quote! {
130         #[automatically_derived]
131         impl #impl_generics Checksum for #name #ty_generics #where_clause {
132             fn checksum<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
133                 #code
134             }
135         }
136     }
137     .into()
138 }
139