1use proc_macro::TokenStream;
3use quote::quote;
4use std::collections::BTreeSet;
5use syn::{
6 Data, DeriveInput, Fields, GenericParam, Lifetime, LifetimeParam, parse_macro_input,
7 parse_quote,
8};
9
10mod enum_expansion;
11
12#[proc_macro_derive(Wire, attributes(wire))]
14pub fn derive_wire(input: TokenStream) -> TokenStream {
15 expand(parse_macro_input!(input as DeriveInput))
16 .unwrap_or_else(syn::Error::into_compile_error)
17 .into()
18}
19
20fn identity(attrs: &[syn::Attribute]) -> syn::Result<u32> {
21 let mut id = None;
22 for attr in attrs.iter().filter(|a| a.path().is_ident("wire")) {
23 attr.parse_nested_meta(|meta| {
24 if !meta.path.is_ident("id") || id.is_some() {
25 return Err(meta.error("expected one wire id"));
26 }
27 id = Some(meta.value()?.parse::<syn::LitInt>()?.base10_parse()?);
28 Ok(())
29 })?;
30 }
31 id.ok_or_else(|| {
32 syn::Error::new(
33 proc_macro2::Span::call_site(),
34 "explicit #[wire(id = N)] required",
35 )
36 })
37}
38
39fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
40 if input
41 .generics
42 .params
43 .iter()
44 .any(|p| !matches!(p, GenericParam::Lifetime(_)))
45 || input.generics.where_clause.is_some()
46 {
47 return Err(syn::Error::new_spanned(
48 input.generics,
49 "Wire supports lifetime parameters only; type/const parameters and where clauses are unsupported",
50 ));
51 }
52 if input.attrs.iter().any(|a| a.path().is_ident("wire")) {
53 return Err(syn::Error::new_spanned(
54 &input.ident,
55 "wire ids belong on fields or variants",
56 ));
57 }
58 let generics = input.generics;
59 let mut decode_name = "__ic_wire_de".to_owned();
60 while generics
61 .lifetimes()
62 .any(|p| p.lifetime.ident == decode_name)
63 {
64 decode_name.push('_');
65 }
66 let decode_lifetime = Lifetime::new(&format!("'{decode_name}"), proc_macro2::Span::call_site());
67 let mut decode_generics = generics.clone();
68 decode_generics.params.insert(
69 0,
70 GenericParam::Lifetime(LifetimeParam::new(decode_lifetime.clone())),
71 );
72 for lifetime in generics.lifetimes().map(|p| &p.lifetime) {
73 decode_generics
74 .make_where_clause()
75 .predicates
76 .push(parse_quote!(#decode_lifetime: #lifetime));
77 }
78 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
79 let (decode_impl_generics, _, decode_where_clause) = decode_generics.split_for_impl();
80 let name = input.ident;
81 let mut ids = BTreeSet::new();
82 let (serialize, deserialize, shape, schema) = match input.data {
83 Data::Struct(data) => {
84 let Fields::Named(fields) = data.fields else {
85 return Err(syn::Error::new_spanned(
86 name,
87 "use a named-field record (empty records are allowed)",
88 ));
89 };
90 if fields.named.len() > 16 {
91 return Err(syn::Error::new_spanned(
92 name,
93 "at most 16 fields; group related fields in a nested record",
94 ));
95 }
96 let mut fields = fields
97 .named
98 .into_iter()
99 .map(|field| {
100 let id = identity(&field.attrs)?;
101 if !ids.insert(id) {
102 return Err(syn::Error::new_spanned(&field, "duplicate wire id"));
103 }
104 Ok((id, field.ident.unwrap(), field.ty))
105 })
106 .collect::<syn::Result<Vec<_>>>()?;
107 fields.sort_by_key(|f| f.0);
108 let names: Vec<_> = fields.iter().map(|f| &f.1).collect();
109 let types: Vec<_> = fields.iter().map(|f| &f.2).collect();
110 let labels: Vec<_> = names
111 .iter()
112 .map(|name| name.to_string().trim_start_matches("r#").to_owned())
113 .collect();
114 let field_ids: Vec<_> = fields.iter().map(|f| f.0).collect();
115 (
116 quote! { ::ic_wire::__private::serde::Serialize::serialize(&(#(&self.#names,)*), serializer) },
117 quote! {
118 let (#(#names,)*): (#(#types,)*) = ::ic_wire::__private::serde::Deserialize::deserialize(deserializer)?;
119 Ok(Self { #(#names,)* })
120 },
121 quote! { ::ic_wire::Shape::record(&[#(<#types as ::ic_wire::Wire>::SHAPE,)*]) },
122 quote! { ::ic_wire::schema::Schema::Record(&[#(::ic_wire::schema::Field { name: #labels, id: #field_ids, value: &<#types as ::ic_wire::Wire>::SCHEMA },)*]) },
123 )
124 }
125 Data::Enum(data) => {
126 enum_expansion::expand(&name, &generics, &decode_generics, &decode_lifetime, data)?
127 }
128 Data::Union(_) => return Err(syn::Error::new_spanned(name, "unions are unsupported")),
129 };
130 Ok(quote! {
131 impl #impl_generics ::ic_wire::__private::serde::Serialize for #name #type_generics #where_clause {
132 fn serialize<S: ::ic_wire::__private::serde::Serializer>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error> { #serialize }
133 }
134 impl #decode_impl_generics ::ic_wire::__private::serde::Deserialize<#decode_lifetime> for #name #type_generics #decode_where_clause {
135 fn deserialize<D: ::ic_wire::__private::serde::Deserializer<#decode_lifetime>>(deserializer: D) -> ::core::result::Result<Self, D::Error> { #deserialize }
136 }
137 impl #impl_generics ::ic_wire::Wire for #name #type_generics #where_clause {
138 const SHAPE: ::ic_wire::Shape = #shape;
139 const SCHEMA: ::ic_wire::schema::Schema = #schema;
140 }
141 })
142}