1use proc_macro::TokenStream;
3use quote::quote;
4use std::collections::BTreeSet;
5use syn::{Data, DeriveInput, Fields, parse_macro_input};
6
7mod enum_expansion;
8
9#[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}