1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::ToTokens;
4use syn::{Error, ItemStruct, LitStr, Result, parse2, visit_mut::visit_item_struct_mut};
5
6mod call_filter_group;
7mod visitor;
8use visitor::*;
9
10#[proc_macro_attribute]
24pub fn freeze_struct(attr: TokenStream, tokens: TokenStream) -> TokenStream {
25 match freeze_struct_impl(attr, tokens) {
26 Ok(item_struct) => item_struct.to_token_stream().into(),
27 Err(err) => err.to_compile_error().into(),
28 }
29}
30
31#[proc_macro]
35pub fn call_filter_group(input: TokenStream) -> TokenStream {
36 let parsed = syn::parse_macro_input!(input as call_filter_group::CallFilterGroupInput);
37 match call_filter_group::generate(parsed) {
38 Ok(tokens) => tokens.into(),
39 Err(err) => err.to_compile_error().into(),
40 }
41}
42
43fn freeze_struct_impl(
44 attr: impl Into<TokenStream2>,
45 tokens: impl Into<TokenStream2>,
46) -> Result<ItemStruct> {
47 let attr = attr.into();
48 let tokens = tokens.into();
49
50 let item = parse2::<ItemStruct>(tokens)?;
51 let mut item_clone = item.clone();
52
53 let mut visitor = CleanDocComments::new();
54 visit_item_struct_mut(&mut visitor, &mut item_clone);
55
56 let calculated_hash = generate_hash(&item_clone);
57 let calculated_hash_hex = format!("{calculated_hash:x}");
58
59 if attr.is_empty() {
60 return Err(Error::new_spanned(
61 item,
62 format!(
63 "You must provide a hashcode in the `freeze_struct` attribute to freeze this struct.\n\n\
64 expected hashcode: `#[freeze_struct(\"{calculated_hash_hex}\")]`"
65 ),
66 ));
67 }
68
69 let parsed_attr = parse2::<LitStr>(attr)?;
70 let provided_hash_hex = parsed_attr.value().to_lowercase();
71
72 if provided_hash_hex != calculated_hash_hex {
73 return Err(Error::new_spanned(
74 item,
75 format!(
76 "You have made a non-trivial change to this struct and the provided hashcode no longer matches:\n{provided_hash_hex} != {calculated_hash_hex}\n\n\
77 If this was intentional, please update the hashcode in the `freeze_struct` attribute to:\n\
78 {calculated_hash_hex}\n\nNote that if you are changing a storage struct in any way, including simply re-ordering fields, \
79 you will need a migration to prevent data corruption."
80 ),
81 ));
82 }
83 Ok(item)
84}