@@ -23,6 +23,44 @@ 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 {
36+ quote:: quote,
37+ syn:: { parse_macro_input, Error , Item } ,
38+ } ;
39+
40+ let item = parse_macro_input ! ( item as Item ) ;
41+
42+ let ident = match item {
43+ Item :: Struct ( ref s) => & s. ident ,
44+ Item :: Enum ( ref e) => & e. ident ,
45+ Item :: Type ( ref t) => & t. ident ,
46+ _ => {
47+ return Error :: new_spanned (
48+ item,
49+ "StableAbi can only be derived for struct, enum, or type alias" ,
50+ )
51+ . to_compile_error ( )
52+ . into ( ) ;
53+ }
54+ } ;
55+
56+ let expanded = quote ! {
57+ #[ automatically_derived]
58+ impl :: solana_frozen_abi:: stable_abi:: StableAbi for #ident { }
59+ } ;
60+
61+ expanded. into ( )
62+ }
63+
2664#[ cfg( feature = "frozen-abi" ) ]
2765use proc_macro2:: { Span , TokenStream as TokenStream2 , TokenTree } ;
2866#[ cfg( feature = "frozen-abi" ) ]
@@ -156,8 +194,8 @@ fn derive_abi_sample_enum_type(input: ItemEnum) -> TokenStream {
156194#[ cfg( feature = "frozen-abi" ) ]
157195fn derive_abi_sample_struct_type ( input : ItemStruct ) -> TokenStream {
158196 let type_name = & input. ident ;
159- let mut sample_fields = quote ! { } ;
160197 let fields = & input. fields ;
198+ let mut sample_fields = quote ! { } ;
161199
162200 match fields {
163201 Fields :: Named ( _) => {
@@ -167,19 +205,15 @@ fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
167205 #field_name: AbiExample :: example( ) ,
168206 } ) ;
169207 }
170- sample_fields = quote ! {
171- { #sample_fields }
172- }
208+ sample_fields = quote ! { { #sample_fields } } ;
173209 }
174210 Fields :: Unnamed ( _) => {
175211 for _ in fields {
176212 sample_fields. extend ( quote ! {
177213 AbiExample :: example( ) ,
178214 } ) ;
179215 }
180- sample_fields = quote ! {
181- ( #sample_fields )
182- }
216+ sample_fields = quote ! { ( #sample_fields ) } ;
183217 }
184218 _ => unimplemented ! ( "fields: {:?}" , fields) ,
185219 }
@@ -195,7 +229,7 @@ fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
195229 impl #impl_generics :: solana_frozen_abi:: abi_example:: AbiExample for #type_name #ty_generics #where_clause {
196230 fn example( ) -> Self {
197231 :: std:: println!(
198- "AbiExample for struct: {}" ,
232+ "AbiExample::example for struct: {}" ,
199233 std:: any:: type_name:: <#type_name #ty_generics>( )
200234 ) ;
201235 use :: solana_frozen_abi:: abi_example:: AbiExample ;
@@ -277,39 +311,61 @@ pub fn derive_abi_enum_visitor(item: TokenStream) -> TokenStream {
277311fn quote_for_test (
278312 test_mod_ident : & Ident ,
279313 type_name : & Ident ,
280- expected_digest : & str ,
314+ expected_api_digest : Option < & str > ,
315+ expected_abi_digest : Option < & str > ,
281316) -> TokenStream2 {
282- quote ! {
283- #[ cfg( test) ]
284- mod #test_mod_ident {
285- use super :: * ;
286- use :: solana_frozen_abi:: abi_example:: { AbiExample , AbiEnumVisitor } ;
287-
317+ let test_api = if expected_api_digest. is_some ( ) {
318+ quote ! {
288319 #[ test]
289- fn test_abi_digest( ) {
320+ fn test_api_digest( ) {
321+ use :: solana_frozen_abi:: abi_example:: { AbiExample , AbiEnumVisitor } ;
290322 let mut digester = :: solana_frozen_abi:: abi_digester:: AbiDigester :: create( ) ;
291323 let example = <#type_name>:: example( ) ;
292324 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- }
297325 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- }
326+ let hash = digester. finalize( ) ;
327+ assert_eq!( #expected_api_digest, :: std:: format!( "{}" , hash) ) ;
328+ }
329+ }
330+ } else {
331+ TokenStream2 :: new ( )
332+ } ;
333+
334+ let test_abi = if expected_abi_digest. is_some ( ) {
335+ quote ! {
336+ #[ test]
337+ fn test_abi_digest( ) {
338+ use :: rand:: { SeedableRng , RngCore } ;
339+ use :: rand_chacha:: ChaCha8Rng ;
340+ use :: bincode;
341+ use :: solana_frozen_abi:: stable_abi:: StableAbi ;
342+
343+ let mut rng = ChaCha8Rng :: seed_from_u64( 20666175621446498 ) ;
344+ let mut digester = :: solana_frozen_abi:: hash:: Hasher :: default ( ) ;
345+
346+ for _ in 0 ..100_000 {
347+ let val = <#type_name>:: random( & mut rng) ;
348+ digester. hash( & bincode:: serialize( & val) . unwrap( ) ) ;
310349 }
350+ assert_eq!( #expected_abi_digest, :: std:: format!( "{}" , digester. result( ) ) ) ;
311351 }
312352 }
353+ } else {
354+ TokenStream2 :: new ( )
355+ } ;
356+
357+ let no_tests = test_api. is_empty ( ) && test_abi. is_empty ( ) ;
358+ if no_tests {
359+ return TokenStream2 :: new ( ) ;
360+ }
361+
362+ quote ! {
363+ #[ cfg( test) ]
364+ mod #test_mod_ident {
365+ use super :: * ;
366+ #test_api
367+ #test_abi
368+ }
313369 }
314370}
315371
@@ -319,9 +375,18 @@ fn test_mod_name(type_name: &Ident) -> Ident {
319375}
320376
321377#[ cfg( feature = "frozen-abi" ) ]
322- fn frozen_abi_type_alias ( input : ItemType , expected_digest : & str ) -> TokenStream {
378+ fn frozen_abi_type_alias (
379+ input : ItemType ,
380+ expected_api_digest : Option < & str > ,
381+ expected_abi_digest : Option < & str > ,
382+ ) -> TokenStream {
323383 let type_name = & input. ident ;
324- let test = quote_for_test ( & test_mod_name ( type_name) , type_name, expected_digest) ;
384+ let test = quote_for_test (
385+ & test_mod_name ( type_name) ,
386+ type_name,
387+ expected_api_digest,
388+ expected_abi_digest,
389+ ) ;
325390 let result = quote ! {
326391 #input
327392 #test
@@ -330,9 +395,18 @@ fn frozen_abi_type_alias(input: ItemType, expected_digest: &str) -> TokenStream
330395}
331396
332397#[ cfg( feature = "frozen-abi" ) ]
333- fn frozen_abi_struct_type ( input : ItemStruct , expected_digest : & str ) -> TokenStream {
398+ fn frozen_abi_struct_type (
399+ input : ItemStruct ,
400+ expected_api_digest : Option < & str > ,
401+ expected_abi_digest : Option < & str > ,
402+ ) -> TokenStream {
334403 let type_name = & input. ident ;
335- let test = quote_for_test ( & test_mod_name ( type_name) , type_name, expected_digest) ;
404+ let test = quote_for_test (
405+ & test_mod_name ( type_name) ,
406+ type_name,
407+ expected_api_digest,
408+ expected_abi_digest,
409+ ) ;
336410 let result = quote ! {
337411 #input
338412 #test
@@ -387,9 +461,18 @@ fn quote_sample_variant(
387461}
388462
389463#[ cfg( feature = "frozen-abi" ) ]
390- fn frozen_abi_enum_type ( input : ItemEnum , expected_digest : & str ) -> TokenStream {
464+ fn frozen_abi_enum_type (
465+ input : ItemEnum ,
466+ expected_api_digest : Option < & str > ,
467+ expected_abi_digest : Option < & str > ,
468+ ) -> TokenStream {
391469 let type_name = & input. ident ;
392- let test = quote_for_test ( & test_mod_name ( type_name) , type_name, expected_digest) ;
470+ let test = quote_for_test (
471+ & test_mod_name ( type_name) ,
472+ type_name,
473+ expected_api_digest,
474+ expected_abi_digest,
475+ ) ;
393476 let result = quote ! {
394477 #input
395478 #test
@@ -400,31 +483,39 @@ fn frozen_abi_enum_type(input: ItemEnum, expected_digest: &str) -> TokenStream {
400483#[ cfg( feature = "frozen-abi" ) ]
401484#[ proc_macro_attribute]
402485pub fn frozen_abi ( attrs : TokenStream , item : TokenStream ) -> TokenStream {
403- let mut expected_digest: Option < String > = None ;
486+ let mut api_expected_digest: Option < String > = None ;
487+ let mut abi_expected_digest: Option < String > = None ;
488+
404489 let attrs_parser = syn:: meta:: parser ( |meta| {
405490 if meta. path . is_ident ( "digest" ) {
406- expected_digest = Some ( meta. value ( ) ?. parse :: < LitStr > ( ) ?. value ( ) ) ;
491+ api_expected_digest = Some ( meta. value ( ) ?. parse :: < LitStr > ( ) ?. value ( ) ) ;
492+ Ok ( ( ) )
493+ } else if meta. path . is_ident ( "abi_digest" ) {
494+ abi_expected_digest = Some ( meta. value ( ) ?. parse :: < LitStr > ( ) ?. value ( ) ) ;
407495 Ok ( ( ) )
408496 } else {
409497 Err ( meta. error ( "unsupported \" frozen_abi\" property" ) )
410498 }
411499 } ) ;
412500 parse_macro_input ! ( attrs with attrs_parser) ;
413501
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-
423502 let item = parse_macro_input ! ( item as Item ) ;
424503 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) ,
504+ Item :: Struct ( input) => frozen_abi_struct_type (
505+ input,
506+ api_expected_digest. as_deref ( ) ,
507+ abi_expected_digest. as_deref ( ) ,
508+ ) ,
509+ Item :: Enum ( input) => frozen_abi_enum_type (
510+ input,
511+ api_expected_digest. as_deref ( ) ,
512+ abi_expected_digest. as_deref ( ) ,
513+ ) ,
514+ Item :: Type ( input) => frozen_abi_type_alias (
515+ input,
516+ api_expected_digest. as_deref ( ) ,
517+ abi_expected_digest. as_deref ( ) ,
518+ ) ,
428519 _ => Error :: new_spanned (
429520 item,
430521 "frozen_abi isn't applicable; only for struct, enum and type" ,
0 commit comments