fuels-macros 0.77.0

Fuel Rust SDK macros to generate types from ABI.
Documentation
use fuels_code_gen::utils::TypePath;
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, quote};
use syn::{Attribute, Error, Expr, ExprLit, Fields, Lit, Meta, Result, Variant};

use crate::parse_utils::has_ignore_attr;

pub(crate) fn get_path_from_attr_or(
    attr_name: &str,
    attrs: &[Attribute],
    default: TokenStream,
) -> Result<TokenStream> {
    let Some(attr) = find_attr(attr_name, attrs) else {
        return Ok(default);
    };

    let Meta::NameValue(name_value) = &attr.meta else {
        return Err(Error::new_spanned(
            attr.meta.path(),
            "expected name='value'",
        ));
    };

    let Expr::Lit(ExprLit {
        lit: Lit::Str(lit_str),
        ..
    }) = &name_value.value
    else {
        return Err(Error::new_spanned(
            &name_value.value,
            "expected string literal",
        ));
    };

    TypePath::new(lit_str.value())
        .map_err(|_| Error::new_spanned(lit_str.value(), "invalid path"))
        .map(|type_path| type_path.to_token_stream())
}

pub(crate) fn find_attr<'a>(name: &str, attrs: &'a [Attribute]) -> Option<&'a Attribute> {
    attrs.iter().find(|attr| {
        attr.path()
            .get_ident()
            .map(|ident| ident == name)
            .unwrap_or(false)
    })
}

pub(crate) struct VariantInfo {
    name: Ident,
    is_unit: bool,
}

pub(crate) enum ExtractedVariant {
    Normal {
        info: VariantInfo,
        discriminant: u64,
    },
    Ignored {
        info: VariantInfo,
    },
}

pub(crate) fn extract_variants(
    contents: impl IntoIterator<Item = Variant>,
    fuels_core_path: TokenStream,
) -> Result<ExtractedVariants> {
    let variants = contents
        .into_iter()
        .enumerate()
        .map(|(discriminant, variant)| -> Result<_> {
            let is_unit = matches!(variant.fields, Fields::Unit);
            if has_ignore_attr(&variant.attrs) {
                Ok(ExtractedVariant::Ignored {
                    info: VariantInfo {
                        name: variant.ident,
                        is_unit,
                    },
                })
            } else {
                validate_variant_type(&variant)?;

                let discriminant = discriminant.try_into().map_err(|_| {
                    Error::new_spanned(&variant.ident, "enums cannot have more than 256 variants")
                })?;

                Ok(ExtractedVariant::Normal {
                    info: VariantInfo {
                        name: variant.ident,
                        is_unit,
                    },
                    discriminant,
                })
            }
        })
        .collect::<Result<_>>()?;

    Ok(ExtractedVariants {
        variants,
        fuels_core_path,
    })
}

pub(crate) struct ExtractedVariants {
    fuels_core_path: TokenStream,
    variants: Vec<ExtractedVariant>,
}

impl ExtractedVariants {
    pub(crate) fn variant_into_discriminant_and_token(&self) -> TokenStream {
        let match_branches = self.variants.iter().map(|variant|
            match variant {
                ExtractedVariant::Normal { info: VariantInfo{ name, is_unit }, discriminant } => {
                    let fuels_core_path = &self.fuels_core_path;
                    if *is_unit {
                            quote! { Self::#name => (#discriminant, #fuels_core_path::traits::Tokenizable::into_token(())) }
                    } else {
                            quote! { Self::#name(inner) => (#discriminant, #fuels_core_path::traits::Tokenizable::into_token(inner))}
                    }
                },
                ExtractedVariant::Ignored { info: VariantInfo{ name, is_unit } } => {
                    let panic_expression = {
                        let name_stringified = name.to_string();
                        quote! {::core::panic!("variant `{}` should never be constructed", #name_stringified)}
                    };
                    if *is_unit {
                        quote! { Self::#name => #panic_expression }
                    } else {
                        quote! { Self::#name(..) => #panic_expression }
                    }
                }
            }
        );

        quote! {
            match self {
                #(#match_branches),*
            }
        }
    }
    pub(crate) fn variant_from_discriminant_and_token(&self, no_std: bool) -> TokenStream {
        let match_discriminant = self
            .variants
            .iter()
            .filter_map(|variant| match variant {
                ExtractedVariant::Normal { info, discriminant } => Some((info, discriminant)),
                _ => None,
            })
            .map(|(VariantInfo { name, is_unit }, discriminant)| {
                let fuels_core_path = &self.fuels_core_path;

                let variant_value = if *is_unit {
                    quote! {}
                } else {
                    quote! { (#fuels_core_path::traits::Tokenizable::from_token(variant_token)?) }
                };

                quote! { #discriminant => ::core::result::Result::Ok(Self::#name #variant_value)}
            });

        let std_lib = std_lib_path(no_std);
        quote! {
            match discriminant {
                #(#match_discriminant,)*
                _ => ::core::result::Result::Err(#std_lib::format!(
                    "discriminant {} doesn't point to any of the enums variants", discriminant
                )),
            }
        }
    }
}

fn validate_variant_type(variant: &Variant) -> Result<()> {
    match &variant.fields {
        Fields::Named(named_fields) => {
            return Err(Error::new_spanned(
                named_fields.clone(),
                "struct like enum variants are not supported".to_string(),
            ));
        }
        Fields::Unnamed(unnamed_fields) => {
            let fields = &unnamed_fields.unnamed;

            if fields.len() != 1 {
                return Err(Error::new_spanned(
                    unnamed_fields.clone(),
                    "tuple-like enum variants must contain exactly one element".to_string(),
                ));
            }
        }
        _ => {}
    }

    Ok(())
}

pub(crate) fn std_lib_path(no_std: bool) -> TokenStream {
    if no_std {
        quote! {::alloc}
    } else {
        quote! {::std}
    }
}