tycho-types-abi-proc 0.3.0

Proc-macro abi helpers for tycho-types
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::Error;

use crate::internals::container;
use crate::internals::container::Container;
use crate::internals::context::Ctxt;
use crate::internals::field::{FieldAttributes, StructField, extract_field_attributes};

pub fn impl_derive(input: syn::DeriveInput) -> Result<TokenStream, Vec<Error>> {
    let ctx = Ctxt::new();
    let container_opt = Container::from_ast(&ctx, &input);

    let Some(container) = container_opt else {
        return Err(ctx.check().unwrap_err());
    };

    let Some((named, struct_fields)) = construct_from_abi(&container.data.fields, &ctx) else {
        return Err(ctx.check().unwrap_err());
    };

    let generics = container::with_bound(
        &container.data.fields,
        &container.generics,
        &syn::parse_quote!(::tycho_types::abi::FromAbi),
    );

    let ident = container.name_ident;
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let slf = if named {
        quote!(Ok(Self { #(#struct_fields),* }))
    } else {
        quote!(Ok(Self ( #(#struct_fields),* )))
    };

    let token_stream = quote! {
        impl #impl_generics ::tycho_types::abi::FromAbi for #ident #ty_generics #where_clause {
            fn from_abi(value: ::tycho_types::abi::AbiValue) -> ::tycho_types::abi::__export::anyhow::Result<Self> {
                let ::tycho_types::abi::AbiValue::Tuple(inner) = value else {
                    return Err(::tycho_types::abi::__export::anyhow::anyhow!(
                        "expected tuple while parsing AbiValue"
                    ));
                };
                let mut __iter = inner.into_iter();
                #slf
            }
        }
    };

    Ok(token_stream)
}

pub fn construct_from_abi(fields: &syn::Fields, ctx: &Ctxt) -> Option<(bool, Vec<TokenStream>)> {
    let mut struct_fields = Vec::new();

    let mut named = true;
    for (index, field) in fields.iter().enumerate() {
        let struct_field = match &field.ident {
            Some(named) => StructField::named(named),
            None => {
                named = false;
                StructField::unnamed(index)
            }
        };

        let attributes = extract_field_attributes(ctx, field.attrs.as_slice());
        if !attributes.extracted {
            return None;
        }

        let token = construct_from_abi_inner(&struct_field, &attributes, named);
        struct_fields.push(token);
    }

    Some((named, struct_fields))
}

fn construct_from_abi_inner(
    struct_field: &StructField,
    attrs: &FieldAttributes,
    named: bool,
) -> TokenStream {
    let field_name = &struct_field.field_name;

    let extractor = if let Some(path) = &attrs.with_handlers.from_abi_handler {
        quote! { #path }
    } else if let Some(path) = &attrs.mod_handler {
        quote! { #path::from_abi }
    } else {
        quote! { <_ as ::tycho_types::abi::FromAbi>::from_abi }
    };

    let base = quote! {
        match __iter.next() {
            Some(__item) => #extractor(__item.value)?,
            None => return Err(::tycho_types::abi::__export::anyhow::anyhow!(
                "not enough tuple items while parsing AbiValue",
            )),
        }
    };

    if named {
        quote! {
            #field_name: #base
        }
    } else {
        base
    }
}