ic-wire-derive 0.4.2

Explicit-layout Wire derives for ic-wire bounded contracts
Documentation
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::BTreeSet;
use syn::{DataEnum, Fields, Ident};

pub(super) fn expand(
    name: &Ident,
    data: DataEnum,
) -> syn::Result<(TokenStream, TokenStream, TokenStream, TokenStream)> {
    let mut ids = BTreeSet::new();
    if data.variants.is_empty() {
        return Err(syn::Error::new_spanned(name, "empty enum is unsupported"));
    }
    let visitor = format_ident!("__IcWireVisitorFor{}", name);
    let mut serializers = Vec::new();
    let mut decoders = Vec::new();
    let mut shapes = Vec::new();
    let mut variants = Vec::new();
    for variant in data.variants {
        let id = super::identity(&variant.attrs)?;
        if !ids.insert(id) {
            return Err(syn::Error::new_spanned(&variant, "duplicate wire id"));
        }
        if variant.discriminant.is_some() {
            return Err(syn::Error::new_spanned(
                &variant,
                "use wire id, not Rust discriminants",
            ));
        }
        let v = variant.ident;
        let label = v.to_string().trim_start_matches("r#").to_owned();
        let (ty, pattern, payload, constructor) = match variant.fields {
            Fields::Unit => (quote!(()), quote!(Self::#v), quote!(()), quote!(#name::#v)),
            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
                let field = &fields.unnamed[0];
                if field.attrs.iter().any(|a| a.path().is_ident("wire")) {
                    return Err(syn::Error::new_spanned(
                        field,
                        "variant payload inherits its variant id",
                    ));
                }
                let ty = &field.ty;
                (
                    quote!(#ty),
                    quote!(Self::#v(value)),
                    quote!(value),
                    quote!(#name::#v(value)),
                )
            }
            _ => {
                return Err(syn::Error::new_spanned(
                    v,
                    "use unit variants or one named-record payload",
                ));
            }
        };
        serializers.push(quote! { #pattern => ::ic_wire::__private::serde::Serialize::serialize(&(#id, #payload), serializer) });
        decoders.push(quote! { #id => {
                    let value: #ty = seq.next_element()?.ok_or_else(|| ::ic_wire::__private::serde::de::Error::custom("missing variant payload"))?;
                    let _ = &value;
                    Ok(#constructor)
                } });
        shapes.push(quote! { <#ty as ::ic_wire::Wire>::SHAPE });
        variants.push(quote! { ::ic_wire::schema::Variant { name: #label, id: #id, value: &<#ty as ::ic_wire::Wire>::SCHEMA } });
    }
    Ok((
        quote! { match self { #(#serializers,)* } },
        quote! {
            struct #visitor;
            impl<'de> ::ic_wire::__private::serde::de::Visitor<'de> for #visitor {
                type Value = #name;
                fn expecting(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.write_str("explicit wire variant") }
                fn visit_seq<A: ::ic_wire::__private::serde::de::SeqAccess<'de>>(self, mut seq: A) -> ::core::result::Result<Self::Value, A::Error> {
                    let tag: u32 = seq.next_element()?.ok_or_else(|| ::ic_wire::__private::serde::de::Error::custom("missing variant id"))?;
                    match tag { #(#decoders,)* _ => Err(::ic_wire::__private::serde::de::Error::custom("unknown variant id")) }
                }
            }
            deserializer.deserialize_tuple(2, #visitor)
        },
        quote! { ::ic_wire::Shape::choice(5, &[#(#shapes,)*]) },
        quote! { ::ic_wire::schema::Schema::Enum(&[#(#variants,)*]) },
    ))
}