@@ -23,6 +23,42 @@ pub fn derive_abi_enum_visitor(_item: TokenStream) -> TokenStream {
2323 "" . parse ( ) . unwrap ( )
2424}
2525
26+ #[ cfg( not( feature = "frozen-abi" ) ) ]
27+ #[ proc_macro_derive( StableAbi ) ]
28+ pub fn derive_stable_abi ( _item : TokenStream ) -> TokenStream {
29+ "" . parse ( ) . unwrap ( )
30+ }
31+
32+ #[ cfg( feature = "frozen-abi" ) ]
33+ #[ proc_macro_derive( StableAbi ) ]
34+ pub fn derive_stable_abi ( item : TokenStream ) -> TokenStream {
35+ use quote:: quote;
36+ use syn:: { parse_macro_input, Error , Item } ;
37+
38+ let item = parse_macro_input ! ( item as Item ) ;
39+
40+ let ident = match item {
41+ Item :: Struct ( ref s) => & s. ident ,
42+ Item :: Enum ( ref e) => & e. ident ,
43+ Item :: Type ( ref t) => & t. ident ,
44+ _ => {
45+ return Error :: new_spanned (
46+ item,
47+ "StableAbi can only be derived for struct, enum, or type alias" ,
48+ )
49+ . to_compile_error ( )
50+ . into ( ) ;
51+ }
52+ } ;
53+
54+ let expanded = quote ! {
55+ #[ automatically_derived]
56+ impl :: solana_frozen_abi:: stable_abi:: StableAbi for #ident { }
57+ } ;
58+
59+ expanded. into ( )
60+ }
61+
2662#[ cfg( feature = "frozen-abi" ) ]
2763use proc_macro2:: { Span , TokenStream as TokenStream2 , TokenTree } ;
2864#[ cfg( feature = "frozen-abi" ) ]
@@ -156,8 +192,8 @@ fn derive_abi_sample_enum_type(input: ItemEnum) -> TokenStream {
156192#[ cfg( feature = "frozen-abi" ) ]
157193fn derive_abi_sample_struct_type ( input : ItemStruct ) -> TokenStream {
158194 let type_name = & input. ident ;
159- let mut sample_fields = quote ! { } ;
160195 let fields = & input. fields ;
196+ let mut sample_fields = quote ! { } ;
161197
162198 match fields {
163199 Fields :: Named ( _) => {
@@ -167,19 +203,15 @@ fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
167203 #field_name: AbiExample :: example( ) ,
168204 } ) ;
169205 }
170- sample_fields = quote ! {
171- { #sample_fields }
172- }
206+ sample_fields = quote ! { { #sample_fields } } ;
173207 }
174208 Fields :: Unnamed ( _) => {
175209 for _ in fields {
176210 sample_fields. extend ( quote ! {
177211 AbiExample :: example( ) ,
178212 } ) ;
179213 }
180- sample_fields = quote ! {
181- ( #sample_fields )
182- }
214+ sample_fields = quote ! { ( #sample_fields ) } ;
183215 }
184216 _ => unimplemented ! ( "fields: {:?}" , fields) ,
185217 }
@@ -195,7 +227,7 @@ fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
195227 impl #impl_generics :: solana_frozen_abi:: abi_example:: AbiExample for #type_name #ty_generics #where_clause {
196228 fn example( ) -> Self {
197229 :: std:: println!(
198- "AbiExample for struct: {}" ,
230+ "AbiExample::example for struct: {}" ,
199231 std:: any:: type_name:: <#type_name #ty_generics>( )
200232 ) ;
201233 use :: solana_frozen_abi:: abi_example:: AbiExample ;
@@ -277,39 +309,61 @@ pub fn derive_abi_enum_visitor(item: TokenStream) -> TokenStream {
277309fn quote_for_test (
278310 test_mod_ident : & Ident ,
279311 type_name : & Ident ,
280- expected_digest : & str ,
312+ expected_api_digest : Option < & str > ,
313+ expected_abi_digest : Option < & str > ,
281314) -> TokenStream2 {
282- quote ! {
283- #[ cfg( test) ]
284- mod #test_mod_ident {
285- use super :: * ;
286- use :: solana_frozen_abi:: abi_example:: { AbiExample , AbiEnumVisitor } ;
287-
315+ let test_api = if expected_api_digest. is_some ( ) {
316+ quote ! {
288317 #[ test]
289- fn test_abi_digest( ) {
318+ fn test_api_digest( ) {
319+ use :: solana_frozen_abi:: abi_example:: { AbiExample , AbiEnumVisitor } ;
290320 let mut digester = :: solana_frozen_abi:: abi_digester:: AbiDigester :: create( ) ;
291321 let example = <#type_name>:: example( ) ;
292322 let result = <_>:: visit_for_abi( &&example, & mut digester) ;
293- let mut hash = digester. finalize( ) ;
294- if result. is_err( ) {
295- :: std:: eprintln!( "Error: digest error: {:#?}" , result) ;
296- }
297323 result. unwrap( ) ;
298- let actual_digest = :: std:: format!( "{}" , hash) ;
299- if :: std:: env:: var( "SOLANA_ABI_BULK_UPDATE" ) . is_ok( ) {
300- if #expected_digest != actual_digest {
301- :: std:: eprintln!( "sed -i -e 's/{}/{}/g' $(git grep --files-with-matches frozen_abi)" , #expected_digest, hash) ;
302- }
303- :: std:: eprintln!( "Warning: Not testing the abi digest under SOLANA_ABI_BULK_UPDATE!" ) ;
304- } else {
305- if let Ok ( dir) = :: std:: env:: var( "SOLANA_ABI_DUMP_DIR" ) {
306- assert_eq!( #expected_digest, actual_digest, "Possibly ABI changed? Examine the diff in SOLANA_ABI_DUMP_DIR!: \n $ diff -u {}/*{}* {}/*{}*" , dir, #expected_digest, dir, actual_digest) ;
307- } else {
308- assert_eq!( #expected_digest, actual_digest, "Possibly ABI changed? Confirm the diff by rerunning before and after this test failed with SOLANA_ABI_DUMP_DIR!" ) ;
309- }
324+ let hash = digester. finalize( ) ;
325+ assert_eq!( #expected_api_digest, format!( "{}" , hash) ) ;
326+ }
327+ }
328+ } else {
329+ TokenStream2 :: new ( )
330+ } ;
331+
332+ let test_abi = if expected_abi_digest. is_some ( ) {
333+ quote ! {
334+ #[ test]
335+ fn test_abi_digest( ) {
336+ use :: rand:: { SeedableRng , RngCore } ;
337+ use :: rand_chacha:: ChaCha8Rng ;
338+ use :: bincode;
339+ use :: solana_frozen_abi:: stable_abi:: StableAbi ;
340+
341+ let mut rng = ChaCha8Rng :: seed_from_u64( 20666175621446498 ) ;
342+ let mut digester = :: solana_frozen_abi:: hash:: Hasher :: default ( ) ;
343+
344+ for _ in 0 ..100_000 {
345+ let val = <#type_name>:: random( & mut rng) ;
346+ digester. hash( & bincode:: serialize( & val) . unwrap( ) ) ;
310347 }
348+ assert_eq!( #expected_abi_digest, format!( "{}" , digester. result( ) ) ) ;
311349 }
312350 }
351+ } else {
352+ TokenStream2 :: new ( )
353+ } ;
354+
355+ let no_tests = test_api. is_empty ( ) && test_abi. is_empty ( ) ;
356+ if no_tests {
357+ return TokenStream2 :: new ( ) ;
358+ }
359+
360+ quote ! {
361+ #[ cfg( test) ]
362+ mod #test_mod_ident {
363+ use super :: * ;
364+ #test_api
365+ #test_abi
366+ }
313367 }
314368}
315369
@@ -319,9 +373,18 @@ fn test_mod_name(type_name: &Ident) -> Ident {
319373}
320374
321375#[ cfg( feature = "frozen-abi" ) ]
322- fn frozen_abi_type_alias ( input : ItemType , expected_digest : & str ) -> TokenStream {
376+ fn frozen_abi_type_alias (
377+ input : ItemType ,
378+ expected_api_digest : Option < & str > ,
379+ expected_abi_digest : Option < & str > ,
380+ ) -> TokenStream {
323381 let type_name = & input. ident ;
324- let test = quote_for_test ( & test_mod_name ( type_name) , type_name, expected_digest) ;
382+ let test = quote_for_test (
383+ & test_mod_name ( type_name) ,
384+ type_name,
385+ expected_api_digest,
386+ expected_abi_digest,
387+ ) ;
325388 let result = quote ! {
326389 #input
327390 #test
@@ -330,9 +393,18 @@ fn frozen_abi_type_alias(input: ItemType, expected_digest: &str) -> TokenStream
330393}
331394
332395#[ cfg( feature = "frozen-abi" ) ]
333- fn frozen_abi_struct_type ( input : ItemStruct , expected_digest : & str ) -> TokenStream {
396+ fn frozen_abi_struct_type (
397+ input : ItemStruct ,
398+ expected_api_digest : Option < & str > ,
399+ expected_abi_digest : Option < & str > ,
400+ ) -> TokenStream {
334401 let type_name = & input. ident ;
335- let test = quote_for_test ( & test_mod_name ( type_name) , type_name, expected_digest) ;
402+ let test = quote_for_test (
403+ & test_mod_name ( type_name) ,
404+ type_name,
405+ expected_api_digest,
406+ expected_abi_digest,
407+ ) ;
336408 let result = quote ! {
337409 #input
338410 #test
@@ -387,9 +459,18 @@ fn quote_sample_variant(
387459}
388460
389461#[ cfg( feature = "frozen-abi" ) ]
390- fn frozen_abi_enum_type ( input : ItemEnum , expected_digest : & str ) -> TokenStream {
462+ fn frozen_abi_enum_type (
463+ input : ItemEnum ,
464+ expected_api_digest : Option < & str > ,
465+ expected_abi_digest : Option < & str > ,
466+ ) -> TokenStream {
391467 let type_name = & input. ident ;
392- let test = quote_for_test ( & test_mod_name ( type_name) , type_name, expected_digest) ;
468+ let test = quote_for_test (
469+ & test_mod_name ( type_name) ,
470+ type_name,
471+ expected_api_digest,
472+ expected_abi_digest,
473+ ) ;
393474 let result = quote ! {
394475 #input
395476 #test
@@ -400,31 +481,39 @@ fn frozen_abi_enum_type(input: ItemEnum, expected_digest: &str) -> TokenStream {
400481#[ cfg( feature = "frozen-abi" ) ]
401482#[ proc_macro_attribute]
402483pub fn frozen_abi ( attrs : TokenStream , item : TokenStream ) -> TokenStream {
403- let mut expected_digest: Option < String > = None ;
484+ let mut api_expected_digest: Option < String > = None ;
485+ let mut abi_expected_digest: Option < String > = None ;
486+
404487 let attrs_parser = syn:: meta:: parser ( |meta| {
405488 if meta. path . is_ident ( "digest" ) {
406- expected_digest = Some ( meta. value ( ) ?. parse :: < LitStr > ( ) ?. value ( ) ) ;
489+ api_expected_digest = Some ( meta. value ( ) ?. parse :: < LitStr > ( ) ?. value ( ) ) ;
490+ Ok ( ( ) )
491+ } else if meta. path . is_ident ( "abi_digest" ) {
492+ abi_expected_digest = Some ( meta. value ( ) ?. parse :: < LitStr > ( ) ?. value ( ) ) ;
407493 Ok ( ( ) )
408494 } else {
409495 Err ( meta. error ( "unsupported \" frozen_abi\" property" ) )
410496 }
411497 } ) ;
412498 parse_macro_input ! ( attrs with attrs_parser) ;
413499
414- let Some ( expected_digest) = expected_digest else {
415- return Error :: new_spanned (
416- TokenStream2 :: from ( item) ,
417- "the required \" digest\" = ... attribute is missing." ,
418- )
419- . to_compile_error ( )
420- . into ( ) ;
421- } ;
422-
423500 let item = parse_macro_input ! ( item as Item ) ;
424501 match item {
425- Item :: Struct ( input) => frozen_abi_struct_type ( input, & expected_digest) ,
426- Item :: Enum ( input) => frozen_abi_enum_type ( input, & expected_digest) ,
427- Item :: Type ( input) => frozen_abi_type_alias ( input, & expected_digest) ,
502+ Item :: Struct ( input) => frozen_abi_struct_type (
503+ input,
504+ api_expected_digest. as_deref ( ) ,
505+ abi_expected_digest. as_deref ( ) ,
506+ ) ,
507+ Item :: Enum ( input) => frozen_abi_enum_type (
508+ input,
509+ api_expected_digest. as_deref ( ) ,
510+ abi_expected_digest. as_deref ( ) ,
511+ ) ,
512+ Item :: Type ( input) => frozen_abi_type_alias (
513+ input,
514+ api_expected_digest. as_deref ( ) ,
515+ abi_expected_digest. as_deref ( ) ,
516+ ) ,
428517 _ => Error :: new_spanned (
429518 item,
430519 "frozen_abi isn't applicable; only for struct, enum and type" ,
0 commit comments