1extern crate proc_macro;
2
3#[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#[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 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 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 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}