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