Skip to main content

gemachain_frozen_abi_macro/
lib.rs

1extern crate proc_macro;
2
3// This file littered with these essential cfgs so ensure them.
4#[cfg(not(any(RUSTC_WITH_SPECIALIZATION, RUSTC_WITHOUT_SPECIALIZATION)))]
5compile_error!("rustc_version is missing in build dependency and build.rs is not specified");
6
7#[cfg(any(RUSTC_WITH_SPECIALIZATION, RUSTC_WITHOUT_SPECIALIZATION))]
8use proc_macro::TokenStream;
9
10// Define dummy macro_attribute and macro_derive for stable rustc
11
12#[cfg(RUSTC_WITHOUT_SPECIALIZATION)]
13#[proc_macro_attribute]
14pub fn frozen_abi(_attrs: TokenStream, item: TokenStream) -> TokenStream {
15    item
16}
17
18#[cfg(RUSTC_WITHOUT_SPECIALIZATION)]
19#[proc_macro_derive(AbiExample)]
20pub fn derive_abi_sample(_item: TokenStream) -> TokenStream {
21    "".parse().unwrap()
22}
23
24#[cfg(RUSTC_WITHOUT_SPECIALIZATION)]
25#[proc_macro_derive(AbiEnumVisitor)]
26pub fn derive_abi_enum_visitor(_item: TokenStream) -> TokenStream {
27    "".parse().unwrap()
28}
29
30#[cfg(RUSTC_WITH_SPECIALIZATION)]
31use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree::Group};
32#[cfg(RUSTC_WITH_SPECIALIZATION)]
33use quote::quote;
34#[cfg(RUSTC_WITH_SPECIALIZATION)]
35use syn::{
36    parse_macro_input, Attribute, AttributeArgs, Error, Fields, Ident, Item, ItemEnum, ItemStruct,
37    ItemType, Lit, Meta, NestedMeta, Variant,
38};
39
40#[cfg(RUSTC_WITH_SPECIALIZATION)]
41fn filter_serde_attrs(attrs: &[Attribute]) -> bool {
42    let mut skip = false;
43
44    for attr in attrs {
45        let ss = &attr.path.segments.first().unwrap().ident.to_string();
46        if ss.starts_with("serde") {
47            for token in attr.tokens.clone() {
48                if let Group(token) = token {
49                    for ident in token.stream() {
50                        if ident.to_string() == "skip" {
51                            skip = true;
52                        }
53                    }
54                }
55            }
56        }
57    }
58
59    skip
60}
61
62#[cfg(RUSTC_WITH_SPECIALIZATION)]
63fn filter_allow_attrs(attrs: &mut Vec<Attribute>) {
64    attrs.retain(|attr| {
65        let ss = &attr.path.segments.first().unwrap().ident.to_string();
66        ss.starts_with("allow")
67    });
68}
69
70#[cfg(RUSTC_WITH_SPECIALIZATION)]
71fn derive_abi_sample_enum_type(input: ItemEnum) -> TokenStream {
72    let type_name = &input.ident;
73
74    let mut sample_variant = quote! {};
75    let mut sample_variant_found = false;
76
77    for variant in &input.variants {
78        let variant_name = &variant.ident;
79        let variant = &variant.fields;
80        if *variant == Fields::Unit {
81            sample_variant.extend(quote! {
82                #type_name::#variant_name
83            });
84        } else if let Fields::Unnamed(variant_fields) = variant {
85            let mut fields = quote! {};
86            for field in &variant_fields.unnamed {
87                if !(field.ident.is_none() && field.colon_token.is_none()) {
88                    unimplemented!("tuple enum: {:?}", field);
89                }
90                let field_type = &field.ty;
91                fields.extend(quote! {
92                    <#field_type>::example(),
93                });
94            }
95            sample_variant.extend(quote! {
96                #type_name::#variant_name(#fields)
97            });
98        } else if let Fields::Named(variant_fields) = variant {
99            let mut fields = quote! {};
100            for field in &variant_fields.named {
101                if field.ident.is_none() || field.colon_token.is_none() {
102                    unimplemented!("tuple enum: {:?}", field);
103                }
104                let field_type = &field.ty;
105                let field_name = &field.ident;
106                fields.extend(quote! {
107                    #field_name: <#field_type>::example(),
108                });
109            }
110            sample_variant.extend(quote! {
111                #type_name::#variant_name{#fields}
112            });
113        } else {
114            unimplemented!("{:?}", variant);
115        }
116
117        if !sample_variant_found {
118            sample_variant_found = true;
119            break;
120        }
121    }
122
123    if !sample_variant_found {
124        unimplemented!("empty enum");
125    }
126
127    let mut attrs = input.attrs.clone();
128    filter_allow_attrs(&mut attrs);
129    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
130
131    let result = quote! {
132        #[automatically_derived]
133        #( #attrs )*
134        impl #impl_generics ::gemachain_frozen_abi::abi_example::AbiExample for #type_name #ty_generics #where_clause {
135            fn example() -> Self {
136                ::log::info!(
137                    "AbiExample for enum: {}",
138                    std::any::type_name::<#type_name #ty_generics>()
139                );
140                #sample_variant
141            }
142        }
143    };
144    result.into()
145}
146
147#[cfg(RUSTC_WITH_SPECIALIZATION)]
148fn derive_abi_sample_struct_type(input: ItemStruct) -> TokenStream {
149    let type_name = &input.ident;
150    let mut sample_fields = quote! {};
151    let fields = &input.fields;
152
153    match fields {
154        Fields::Named(_) => {
155            for field in fields {
156                let field_name = &field.ident;
157                sample_fields.extend(quote! {
158                    #field_name: AbiExample::example(),
159                });
160            }
161            sample_fields = quote! {
162                { #sample_fields }
163            }
164        }
165        Fields::Unnamed(_) => {
166            for _ in fields {
167                sample_fields.extend(quote! {
168                    AbiExample::example(),
169                });
170            }
171            sample_fields = quote! {
172                ( #sample_fields )
173            }
174        }
175        _ => unimplemented!("fields: {:?}", fields),
176    }
177
178    let mut attrs = input.attrs.clone();
179    filter_allow_attrs(&mut attrs);
180    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
181    let turbofish = ty_generics.as_turbofish();
182
183    let result = quote! {
184        #[automatically_derived]
185        #( #attrs )*
186        impl #impl_generics ::gemachain_frozen_abi::abi_example::AbiExample for #type_name #ty_generics #where_clause {
187            fn example() -> Self {
188                ::log::info!(
189                    "AbiExample for struct: {}",
190                    std::any::type_name::<#type_name #ty_generics>()
191                );
192                use ::gemachain_frozen_abi::abi_example::AbiExample;
193
194                #type_name #turbofish #sample_fields
195            }
196        }
197    };
198
199    result.into()
200}
201
202#[cfg(RUSTC_WITH_SPECIALIZATION)]
203#[proc_macro_derive(AbiExample)]
204pub fn derive_abi_sample(item: TokenStream) -> TokenStream {
205    let item = parse_macro_input!(item as Item);
206
207    match item {
208        Item::Struct(input) => derive_abi_sample_struct_type(input),
209        Item::Enum(input) => derive_abi_sample_enum_type(input),
210        _ => Error::new_spanned(item, "AbiSample isn't applicable; only for struct and enum")
211            .to_compile_error()
212            .into(),
213    }
214}
215
216#[cfg(RUSTC_WITH_SPECIALIZATION)]
217fn do_derive_abi_enum_visitor(input: ItemEnum) -> TokenStream {
218    let type_name = &input.ident;
219    let mut serialized_variants = quote! {};
220    let mut variant_count: u64 = 0;
221    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
222    for variant in &input.variants {
223        // Don't digest a variant with serde(skip)
224        if filter_serde_attrs(&variant.attrs) {
225            continue;
226        };
227        let sample_variant = quote_sample_variant(type_name, &ty_generics, variant);
228        variant_count = if let Some(variant_count) = variant_count.checked_add(1) {
229            variant_count
230        } else {
231            break;
232        };
233        serialized_variants.extend(quote! {
234            #sample_variant;
235            Serialize::serialize(&sample_variant, digester.create_enum_child()?)?;
236        });
237    }
238
239    let type_str = format!("{}", type_name);
240    (quote! {
241        impl #impl_generics ::gemachain_frozen_abi::abi_example::AbiEnumVisitor for #type_name #ty_generics #where_clause {
242            fn visit_for_abi(&self, digester: &mut ::gemachain_frozen_abi::abi_digester::AbiDigester) -> ::gemachain_frozen_abi::abi_digester::DigestResult {
243                let enum_name = #type_str;
244                use ::serde::ser::Serialize;
245                use ::gemachain_frozen_abi::abi_example::AbiExample;
246                digester.update_with_string(format!("enum {} (variants = {})", enum_name, #variant_count));
247                #serialized_variants
248                digester.create_child()
249            }
250        }
251    }).into()
252}
253
254#[cfg(RUSTC_WITH_SPECIALIZATION)]
255#[proc_macro_derive(AbiEnumVisitor)]
256pub fn derive_abi_enum_visitor(item: TokenStream) -> TokenStream {
257    let item = parse_macro_input!(item as Item);
258
259    match item {
260        Item::Enum(input) => do_derive_abi_enum_visitor(input),
261        _ => Error::new_spanned(item, "AbiEnumVisitor not applicable; only for enum")
262            .to_compile_error()
263            .into(),
264    }
265}
266
267#[cfg(RUSTC_WITH_SPECIALIZATION)]
268fn quote_for_test(
269    test_mod_ident: &Ident,
270    type_name: &Ident,
271    expected_digest: &str,
272) -> TokenStream2 {
273    // escape from nits.sh...
274    let p = Ident::new(&("ep".to_owned() + "rintln"), Span::call_site());
275    quote! {
276        #[cfg(test)]
277        mod #test_mod_ident {
278            use super::*;
279            use ::gemachain_frozen_abi::abi_example::{AbiExample, AbiEnumVisitor};
280
281            #[test]
282            fn test_abi_digest() {
283                ::gemachain_logger::setup();
284                let mut digester = ::gemachain_frozen_abi::abi_digester::AbiDigester::create();
285                let example = <#type_name>::example();
286                let result = <_>::visit_for_abi(&&example, &mut digester);
287                let mut hash = digester.finalize();
288                // pretty-print error
289                if result.is_err() {
290                    ::log::error!("digest error: {:#?}", result);
291                }
292                result.unwrap();
293                let actual_digest = format!("{}", hash);
294                if ::std::env::var("GEMACHAIN_ABI_BULK_UPDATE").is_ok() {
295                    if #expected_digest != actual_digest {
296                        #p!("sed -i -e 's/{}/{}/g' $(git grep --files-with-matches frozen_abi)", #expected_digest, hash);
297                    }
298                    ::log::warn!("Not testing the abi digest under GEMACHAIN_ABI_BULK_UPDATE!");
299                } else {
300                    if let Ok(dir) = ::std::env::var("GEMACHAIN_ABI_DUMP_DIR") {
301                        assert_eq!(#expected_digest, actual_digest, "Possibly ABI changed? Examine the diff in GEMACHAIN_ABI_DUMP_DIR!: $ diff -u {}/*{}* {}/*{}*", dir, #expected_digest, dir, actual_digest);
302                    } else {
303                        assert_eq!(#expected_digest, actual_digest, "Possibly ABI changed? Confirm the diff by rerunning before and after this test failed with GEMACHAIN_ABI_DUMP_DIR!");
304                    }
305                }
306            }
307        }
308    }
309}
310
311#[cfg(RUSTC_WITH_SPECIALIZATION)]
312fn test_mod_name(type_name: &Ident) -> Ident {
313    Ident::new(
314        &format!("{}_frozen_abi", type_name.to_string()),
315        Span::call_site(),
316    )
317}
318
319#[cfg(RUSTC_WITH_SPECIALIZATION)]
320fn frozen_abi_type_alias(input: ItemType, expected_digest: &str) -> TokenStream {
321    let type_name = &input.ident;
322    let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
323    let result = quote! {
324        #input
325        #test
326    };
327    result.into()
328}
329
330#[cfg(RUSTC_WITH_SPECIALIZATION)]
331fn frozen_abi_struct_type(input: ItemStruct, expected_digest: &str) -> TokenStream {
332    let type_name = &input.ident;
333    let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
334    let result = quote! {
335        #input
336        #test
337    };
338    result.into()
339}
340
341#[cfg(RUSTC_WITH_SPECIALIZATION)]
342fn quote_sample_variant(
343    type_name: &Ident,
344    ty_generics: &syn::TypeGenerics,
345    variant: &Variant,
346) -> TokenStream2 {
347    let variant_name = &variant.ident;
348    let variant = &variant.fields;
349    if *variant == Fields::Unit {
350        quote! {
351            let sample_variant: #type_name #ty_generics = #type_name::#variant_name;
352        }
353    } else if let Fields::Unnamed(variant_fields) = variant {
354        let mut fields = quote! {};
355        for field in &variant_fields.unnamed {
356            if !(field.ident.is_none() && field.colon_token.is_none()) {
357                unimplemented!();
358            }
359            let ty = &field.ty;
360            fields.extend(quote! {
361                <#ty>::example(),
362            });
363        }
364        quote! {
365            let sample_variant: #type_name #ty_generics = #type_name::#variant_name(#fields);
366        }
367    } else if let Fields::Named(variant_fields) = variant {
368        let mut fields = quote! {};
369        for field in &variant_fields.named {
370            if field.ident.is_none() || field.colon_token.is_none() {
371                unimplemented!();
372            }
373            let field_type_name = &field.ty;
374            let field_name = &field.ident;
375            fields.extend(quote! {
376                #field_name: <#field_type_name>::example(),
377            });
378        }
379        quote! {
380            let sample_variant: #type_name #ty_generics = #type_name::#variant_name{#fields};
381        }
382    } else {
383        unimplemented!("variant: {:?}", variant)
384    }
385}
386
387#[cfg(RUSTC_WITH_SPECIALIZATION)]
388fn frozen_abi_enum_type(input: ItemEnum, expected_digest: &str) -> TokenStream {
389    let type_name = &input.ident;
390    let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
391    let result = quote! {
392        #input
393        #test
394    };
395    result.into()
396}
397
398#[cfg(RUSTC_WITH_SPECIALIZATION)]
399#[proc_macro_attribute]
400pub fn frozen_abi(attrs: TokenStream, item: TokenStream) -> TokenStream {
401    let args = parse_macro_input!(attrs as AttributeArgs);
402    let mut expected_digest: Option<String> = None;
403    for arg in args {
404        match arg {
405            NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("digest") => {
406                if let Lit::Str(lit) = nv.lit {
407                    expected_digest = Some(lit.value());
408                }
409            }
410            _ => {}
411        }
412    }
413    let expected_digest = expected_digest.expect("the required \"digest\" = ... is missing.");
414
415    let item = parse_macro_input!(item as Item);
416    match item {
417        Item::Struct(input) => frozen_abi_struct_type(input, &expected_digest),
418        Item::Enum(input) => frozen_abi_enum_type(input, &expected_digest),
419        Item::Type(input) => frozen_abi_type_alias(input, &expected_digest),
420        _ => Error::new_spanned(
421            item,
422            "frozen_abi isn't applicable; only for struct, enum and type",
423        )
424        .to_compile_error()
425        .into(),
426    }
427}