Skip to main content

ic_wire_derive/
lib.rs

1//! Explicit-layout derives for IC Wire contracts.
2use proc_macro::TokenStream;
3use quote::quote;
4use std::collections::BTreeSet;
5use syn::{Data, DeriveInput, Fields, parse_macro_input};
6
7mod enum_expansion;
8
9/// Derive a bounded record or tagged enum. Fields/variants require `#[wire(id = N)]`.
10#[proc_macro_derive(Wire, attributes(wire))]
11pub fn derive_wire(input: TokenStream) -> TokenStream {
12    expand(parse_macro_input!(input as DeriveInput))
13        .unwrap_or_else(syn::Error::into_compile_error)
14        .into()
15}
16
17fn identity(attrs: &[syn::Attribute]) -> syn::Result<u32> {
18    let mut id = None;
19    for attr in attrs.iter().filter(|a| a.path().is_ident("wire")) {
20        attr.parse_nested_meta(|meta| {
21            if !meta.path.is_ident("id") || id.is_some() {
22                return Err(meta.error("expected one wire id"));
23            }
24            id = Some(meta.value()?.parse::<syn::LitInt>()?.base10_parse()?);
25            Ok(())
26        })?;
27    }
28    id.ok_or_else(|| {
29        syn::Error::new(
30            proc_macro2::Span::call_site(),
31            "explicit #[wire(id = N)] required",
32        )
33    })
34}
35
36fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
37    if !input.generics.params.is_empty() || input.generics.where_clause.is_some() {
38        return Err(syn::Error::new_spanned(
39            input.generics,
40            "Wire requires concrete contracts",
41        ));
42    }
43    if input.attrs.iter().any(|a| a.path().is_ident("wire")) {
44        return Err(syn::Error::new_spanned(
45            &input.ident,
46            "wire ids belong on fields or variants",
47        ));
48    }
49    let name = input.ident;
50    let mut ids = BTreeSet::new();
51    let (serialize, deserialize, shape, schema) = match input.data {
52        Data::Struct(data) => {
53            let Fields::Named(fields) = data.fields else {
54                return Err(syn::Error::new_spanned(
55                    name,
56                    "use a named-field record (empty records are allowed)",
57                ));
58            };
59            if fields.named.len() > 16 {
60                return Err(syn::Error::new_spanned(
61                    name,
62                    "at most 16 fields; group related fields in a nested record",
63                ));
64            }
65            let mut fields = fields
66                .named
67                .into_iter()
68                .map(|field| {
69                    let id = identity(&field.attrs)?;
70                    if !ids.insert(id) {
71                        return Err(syn::Error::new_spanned(&field, "duplicate wire id"));
72                    }
73                    Ok((id, field.ident.unwrap(), field.ty))
74                })
75                .collect::<syn::Result<Vec<_>>>()?;
76            fields.sort_by_key(|f| f.0);
77            let names: Vec<_> = fields.iter().map(|f| &f.1).collect();
78            let types: Vec<_> = fields.iter().map(|f| &f.2).collect();
79            let labels: Vec<_> = names
80                .iter()
81                .map(|name| name.to_string().trim_start_matches("r#").to_owned())
82                .collect();
83            let field_ids: Vec<_> = fields.iter().map(|f| f.0).collect();
84            (
85                quote! { ::ic_wire::__private::serde::Serialize::serialize(&(#(&self.#names,)*), serializer) },
86                quote! {
87                    let (#(#names,)*): (#(#types,)*) = ::ic_wire::__private::serde::Deserialize::deserialize(deserializer)?;
88                    Ok(Self { #(#names,)* })
89                },
90                quote! { ::ic_wire::Shape::record(&[#(<#types as ::ic_wire::Wire>::SHAPE,)*]) },
91                quote! { ::ic_wire::schema::Schema::Record(&[#(::ic_wire::schema::Field { name: #labels, id: #field_ids, value: &<#types as ::ic_wire::Wire>::SCHEMA },)*]) },
92            )
93        }
94        Data::Enum(data) => enum_expansion::expand(&name, data)?,
95        Data::Union(_) => return Err(syn::Error::new_spanned(name, "unions are unsupported")),
96    };
97    Ok(quote! {
98        impl ::ic_wire::__private::serde::Serialize for #name {
99            fn serialize<S: ::ic_wire::__private::serde::Serializer>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error> { #serialize }
100        }
101        impl<'de> ::ic_wire::__private::serde::Deserialize<'de> for #name {
102            fn deserialize<D: ::ic_wire::__private::serde::Deserializer<'de>>(deserializer: D) -> ::core::result::Result<Self, D::Error> { #deserialize }
103        }
104        impl ::ic_wire::Wire for #name {
105            const SHAPE: ::ic_wire::Shape = #shape;
106            const SCHEMA: ::ic_wire::schema::Schema = #schema;
107        }
108    })
109}