cbe_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 ::cbe_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 ::cbe_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 ::cbe_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 ::cbe_frozen_abi::abi_example::AbiEnumVisitor for #type_name #ty_generics #where_clause {
242            fn visit_for_abi(&self, digester: &mut ::cbe_frozen_abi::abi_digester::AbiDigester) -> ::cbe_frozen_abi::abi_digester::DigestResult {
243                let enum_name = #type_str;
244                use ::serde::ser::Serialize;
245                use ::cbe_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 ::cbe_frozen_abi::abi_example::{AbiExample, AbiEnumVisitor};
280
281            #[test]
282            fn test_abi_digest() {
283                ::cbe_logger::setup();
284                let mut digester = ::cbe_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("CBE_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 CBE_ABI_BULK_UPDATE!");
299                } else {
300                    if let Ok(dir) = ::std::env::var("CBE_ABI_DUMP_DIR") {
301                        assert_eq!(#expected_digest, actual_digest, "Possibly ABI changed? Examine the diff in CBE_ABI_DUMP_DIR!: \n$ 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 CBE_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(&format!("{type_name}_frozen_abi"), Span::call_site())
314}
315
316#[cfg(RUSTC_WITH_SPECIALIZATION)]
317fn frozen_abi_type_alias(input: ItemType, expected_digest: &str) -> TokenStream {
318    let type_name = &input.ident;
319    let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
320    let result = quote! {
321        #input
322        #test
323    };
324    result.into()
325}
326
327#[cfg(RUSTC_WITH_SPECIALIZATION)]
328fn frozen_abi_struct_type(input: ItemStruct, expected_digest: &str) -> TokenStream {
329    let type_name = &input.ident;
330    let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
331    let result = quote! {
332        #input
333        #test
334    };
335    result.into()
336}
337
338#[cfg(RUSTC_WITH_SPECIALIZATION)]
339fn quote_sample_variant(
340    type_name: &Ident,
341    ty_generics: &syn::TypeGenerics,
342    variant: &Variant,
343) -> TokenStream2 {
344    let variant_name = &variant.ident;
345    let variant = &variant.fields;
346    if *variant == Fields::Unit {
347        quote! {
348            let sample_variant: #type_name #ty_generics = #type_name::#variant_name;
349        }
350    } else if let Fields::Unnamed(variant_fields) = variant {
351        let mut fields = quote! {};
352        for field in &variant_fields.unnamed {
353            if !(field.ident.is_none() && field.colon_token.is_none()) {
354                unimplemented!();
355            }
356            let ty = &field.ty;
357            fields.extend(quote! {
358                <#ty>::example(),
359            });
360        }
361        quote! {
362            let sample_variant: #type_name #ty_generics = #type_name::#variant_name(#fields);
363        }
364    } else if let Fields::Named(variant_fields) = variant {
365        let mut fields = quote! {};
366        for field in &variant_fields.named {
367            if field.ident.is_none() || field.colon_token.is_none() {
368                unimplemented!();
369            }
370            let field_type_name = &field.ty;
371            let field_name = &field.ident;
372            fields.extend(quote! {
373                #field_name: <#field_type_name>::example(),
374            });
375        }
376        quote! {
377            let sample_variant: #type_name #ty_generics = #type_name::#variant_name{#fields};
378        }
379    } else {
380        unimplemented!("variant: {:?}", variant)
381    }
382}
383
384#[cfg(RUSTC_WITH_SPECIALIZATION)]
385fn frozen_abi_enum_type(input: ItemEnum, expected_digest: &str) -> TokenStream {
386    let type_name = &input.ident;
387    let test = quote_for_test(&test_mod_name(type_name), type_name, expected_digest);
388    let result = quote! {
389        #input
390        #test
391    };
392    result.into()
393}
394
395#[cfg(RUSTC_WITH_SPECIALIZATION)]
396#[proc_macro_attribute]
397pub fn frozen_abi(attrs: TokenStream, item: TokenStream) -> TokenStream {
398    let args = parse_macro_input!(attrs as AttributeArgs);
399    let mut expected_digest: Option<String> = None;
400    for arg in args {
401        match arg {
402            NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("digest") => {
403                if let Lit::Str(lit) = nv.lit {
404                    expected_digest = Some(lit.value());
405                }
406            }
407            _ => {}
408        }
409    }
410    let expected_digest = expected_digest.expect("the required \"digest\" = ... is missing.");
411
412    let item = parse_macro_input!(item as Item);
413    match item {
414        Item::Struct(input) => frozen_abi_struct_type(input, &expected_digest),
415        Item::Enum(input) => frozen_abi_enum_type(input, &expected_digest),
416        Item::Type(input) => frozen_abi_type_alias(input, &expected_digest),
417        _ => Error::new_spanned(
418            item,
419            "frozen_abi isn't applicable; only for struct, enum and type",
420        )
421        .to_compile_error()
422        .into(),
423    }
424}