multiversx-sc-codec-derive 0.25.0

Macro implementations of multiversx-sc-codec #[derive(NestedEncode, NestedDecode, TopEncode, TopDecode)]
Documentation
use quote::{ToTokens, quote};
use syn::{Variant, punctuated::Punctuated, token::Comma};

pub const BITFLAGS_PRIMITIVE: &str = ":: __private :: PublicFlags > :: Primitive";
const BITFLAGS_INTERNAL: &str = ":: __private :: PublicFlags > :: Internal";
const BITFLAGS_INTERNAL_PATH: &str = ":: __private :: PublicFlags :: Internal";
const PRIMITIVE: &str = "Primitive";

pub struct ExplicitDiscriminant {
    pub variant_index: usize,
    pub value: u8,
}

pub fn is_fieldless_enum(data_enum: &syn::DataEnum) -> bool {
    data_enum
        .variants
        .iter()
        .all(|variant| variant.fields.is_empty())
}

pub fn is_bitflags_struct(data_struct: &syn::DataStruct) -> bool {
    data_struct.fields.iter().any(|field| {
        field
            .ty
            .to_token_stream()
            .to_string()
            .contains(BITFLAGS_INTERNAL)
    })
}

pub fn self_field_expr(index: usize, field: &syn::Field) -> proc_macro2::TokenStream {
    match &field.ident {
        Some(ident) => quote!(self.#ident),
        None => {
            let index_lit = proc_macro2::Literal::usize_unsuffixed(index);

            if field
                .ty
                .to_token_stream()
                .to_string()
                .contains(BITFLAGS_INTERNAL)
            {
                quote!(self.#index_lit.bits())
            } else {
                quote!(self.#index_lit)
            }
        }
    }
}

pub fn local_variable_for_field(index: usize, field: &syn::Field) -> proc_macro2::TokenStream {
    if let Some(ident) = &field.ident {
        quote! {
            #ident
        }
    } else {
        let local_var_name = format!("unnamed_{index}");
        let local_var_ident = syn::Ident::new(&local_var_name, proc_macro2::Span::call_site());
        quote! {
            #local_var_ident
        }
    }
}

pub fn fields_snippets<F>(fields: &syn::Fields, field_mapper: F) -> Vec<proc_macro2::TokenStream>
where
    F: Fn(usize, &syn::Field) -> proc_macro2::TokenStream,
{
    match fields {
        syn::Fields::Named(fields_named) => fields_named
            .named
            .iter()
            .enumerate()
            .map(|(index, field)| field_mapper(index, field))
            .collect(),
        syn::Fields::Unnamed(fields_unnamed) => fields_unnamed
            .unnamed
            .iter()
            .enumerate()
            .map(|(index, field)| field_mapper(index, field))
            .collect(),
        syn::Fields::Unit => Vec::new(),
    }
}

pub fn fields_decl_syntax<F>(fields: &syn::Fields, field_mapper: F) -> proc_macro2::TokenStream
where
    F: Fn(usize, &syn::Field) -> proc_macro2::TokenStream,
{
    match fields {
        syn::Fields::Named(fields_named) => {
            let local_variables: Vec<proc_macro2::TokenStream> = fields_named
                .named
                .iter()
                .enumerate()
                .map(|(index, field)| field_mapper(index, field))
                .collect();
            quote! {
                { #(#local_variables),* }
            }
        }
        syn::Fields::Unnamed(fields_unnamed) => {
            let local_variables: Vec<proc_macro2::TokenStream> = fields_unnamed
                .unnamed
                .iter()
                .enumerate()
                .map(|(index, field)| field_mapper(index, field))
                .collect();
            quote! {
                ( #(#local_variables),* )
            }
        }
        syn::Fields::Unit => quote! {},
    }
}

pub fn validate_enum_variants(variants: &Punctuated<Variant, Comma>) {
    assert!(
        variants.len() <= 256,
        "enums with more than 256 variants not supported"
    );
}

pub fn get_discriminant(
    variant_index: usize,
    variant: &syn::Variant,
    previous_disc: &mut Vec<ExplicitDiscriminant>,
) -> proc_macro2::TokenStream {
    // if it has explicit discriminant
    if let Some((_, syn::Expr::Lit(expr))) = &variant.discriminant {
        let lit = match &expr.lit {
            syn::Lit::Int(val) => {
                let value = val.base10_parse().unwrap_or_else(|_| {
                    panic!("Can not unwrap int value from explicit discriminant")
                });
                previous_disc.push(ExplicitDiscriminant {
                    variant_index,
                    value,
                });
                value
            }
            _ => panic!("Only integer values as discriminants"), // theoretically covered by the compiler
        };
        return quote! { #lit};
    }

    // if no explicit discriminant, check previous discriminants
    // get previous explicit + 1 if there has been any explicit before
    let next_value = match previous_disc.last() {
        //there are previous explicit discriminants
        Some(ExplicitDiscriminant {
            variant_index: prev_index,
            value: prev_value,
        }) if *prev_index < variant_index - 1 => prev_value + (variant_index - prev_index) as u8,
        Some(ExplicitDiscriminant {
            variant_index: _,
            value: prev_value,
        }) => prev_value + 1,

        // vec is empty, return index
        None => variant_index as u8,
    };

    quote! { #next_value}
}

pub fn sanitize_type_path(mut field: syn::Type) -> proc_macro2::TokenStream {
    if let syn::Type::Path(ref mut p) = field {
        if p.path
            .to_token_stream()
            .to_string()
            .contains(BITFLAGS_INTERNAL_PATH)
        {
            let modified_path = p.path.segments.last_mut().unwrap();
            modified_path.ident = syn::Ident::new(PRIMITIVE, modified_path.ident.span());
        }
    }

    quote! (#field)
}