1use proc_macro::TokenStream;
3use quote::{ToTokens, format_ident, 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 fresh_ident(source: &str, base: &str) -> syn::Ident {
41 let mut name = base.to_owned();
42 while source.contains(&name) {
43 name.push('_');
44 }
45 format_ident!("{name}")
46}
47
48fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
49 if input
50 .generics
51 .params
52 .iter()
53 .any(|p| !matches!(p, GenericParam::Lifetime(_)))
54 || input.generics.where_clause.is_some()
55 {
56 return Err(syn::Error::new_spanned(
57 input.generics,
58 "Wire supports lifetime parameters only; type/const parameters and where clauses are unsupported",
59 ));
60 }
61 if input.attrs.iter().any(|a| a.path().is_ident("wire")) {
62 return Err(syn::Error::new_spanned(
63 &input.ident,
64 "wire ids belong on fields or variants",
65 ));
66 }
67 let source = input.to_token_stream().to_string();
68 let serializer = fresh_ident(&source, "S");
69 let deserializer = fresh_ident(&source, "D");
70 let serializer_binding = fresh_ident(&source, "__ic_wire_serializer");
71 let deserializer_binding = fresh_ident(&source, "__ic_wire_deserializer");
72 let fields_binding = fresh_ident(&source, "__ic_wire_fields");
73 let generics = input.generics;
74 let mut decode_name = "__ic_wire_de".to_owned();
75 while generics
76 .lifetimes()
77 .any(|p| p.lifetime.ident == decode_name)
78 {
79 decode_name.push('_');
80 }
81 let decode_lifetime = Lifetime::new(&format!("'{decode_name}"), proc_macro2::Span::call_site());
82 let mut decode_generics = generics.clone();
83 decode_generics.params.insert(
84 0,
85 GenericParam::Lifetime(LifetimeParam::new(decode_lifetime.clone())),
86 );
87 for lifetime in generics.lifetimes().map(|p| &p.lifetime) {
88 decode_generics
89 .make_where_clause()
90 .predicates
91 .push(parse_quote!(#decode_lifetime: #lifetime));
92 }
93 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
94 let (decode_impl_generics, _, decode_where_clause) = decode_generics.split_for_impl();
95 let name = input.ident;
96 let mut ids = BTreeSet::new();
97 let canonical_binding = fresh_ident(&source, "__ic_wire_canonical");
98 let mut canonical_fields = quote!(false);
99 let mut canonical = quote! { #canonical_binding.compare(self) };
100 let (serialize, deserialize, shape, schema) = match input.data {
101 Data::Struct(data) => {
102 let Fields::Named(fields) = data.fields else {
103 return Err(syn::Error::new_spanned(
104 name,
105 "use a named-field record (empty records are allowed)",
106 ));
107 };
108 if fields.named.len() > 16 {
109 return Err(syn::Error::new_spanned(
110 name,
111 "at most 16 fields; group related fields in a nested record",
112 ));
113 }
114 let mut fields = fields
115 .named
116 .into_iter()
117 .map(|field| {
118 let id = identity(&field.attrs)?;
119 if !ids.insert(id) {
120 return Err(syn::Error::new_spanned(&field, "duplicate wire id"));
121 }
122 Ok((id, field.ident.unwrap(), field.ty))
123 })
124 .collect::<syn::Result<Vec<_>>>()?;
125 fields.sort_by_key(|f| f.0);
126 let names: Vec<_> = fields.iter().map(|f| &f.1).collect();
127 let types: Vec<_> = fields.iter().map(|f| &f.2).collect();
128 let labels: Vec<_> = names
129 .iter()
130 .map(|name| name.to_string().trim_start_matches("r#").to_owned())
131 .collect();
132 let indices: Vec<_> = (0..fields.len()).map(syn::Index::from).collect();
133 let field_ids: Vec<_> = fields.iter().map(|f| f.0).collect();
134 canonical_fields = quote!(true #(&& <#types as ::ic_wire::Wire>::__CANONICAL_FIELDS)*);
135 canonical = quote! {
136 if !<Self as ::ic_wire::Wire>::__CANONICAL_FIELDS {
137 return #canonical_binding.compare(self);
138 }
139 #(::ic_wire::Wire::__canonical(&self.#names, #canonical_binding)?;)*
140 ::core::result::Result::Ok(())
141 };
142 (
143 quote! { ::ic_wire::__private::serde::Serialize::serialize(&(#(&self.#names,)*), #serializer_binding) },
144 quote! {
145 let #fields_binding: (#(#types,)*) = ::ic_wire::__private::serde::Deserialize::deserialize(#deserializer_binding)?;
146 ::core::result::Result::Ok(Self { #(#names: #fields_binding.#indices,)* })
147 },
148 quote! { ::ic_wire::Shape::record(&[#(<#types as ::ic_wire::Wire>::SHAPE,)*]) },
149 quote! { ::ic_wire::schema::Schema::Record(&[#(::ic_wire::schema::Field { name: #labels, id: #field_ids, value: &<#types as ::ic_wire::Wire>::SCHEMA },)*]) },
150 )
151 }
152 Data::Enum(data) => enum_expansion::expand(
153 &name,
154 &generics,
155 &decode_generics,
156 &decode_lifetime,
157 data,
158 &source,
159 (&serializer_binding, &deserializer_binding),
160 )?,
161 Data::Union(_) => return Err(syn::Error::new_spanned(name, "unions are unsupported")),
162 };
163 Ok(quote! {
164 impl #impl_generics ::ic_wire::__private::serde::Serialize for #name #type_generics #where_clause {
165 fn serialize<#serializer: ::ic_wire::__private::serde::Serializer>(&self, #serializer_binding: #serializer) -> ::core::result::Result<<#serializer as ::ic_wire::__private::serde::Serializer>::Ok, <#serializer as ::ic_wire::__private::serde::Serializer>::Error> { #serialize }
166 }
167 impl #decode_impl_generics ::ic_wire::__private::serde::Deserialize<#decode_lifetime> for #name #type_generics #decode_where_clause {
168 fn deserialize<#deserializer: ::ic_wire::__private::serde::Deserializer<#decode_lifetime>>(#deserializer_binding: #deserializer) -> ::core::result::Result<Self, <#deserializer as ::ic_wire::__private::serde::Deserializer<#decode_lifetime>>::Error> { #deserialize }
169 }
170 impl #impl_generics ::ic_wire::Wire for #name #type_generics #where_clause {
171 const __CANONICAL_FIELDS: ::core::primitive::bool = #canonical_fields;
172 fn __canonical(&self, #canonical_binding: &mut ::ic_wire::__private::Canonical<'_>) -> ::core::result::Result<(), ::ic_wire::Error> {
173 #canonical
174 }
175 const SHAPE: ::ic_wire::Shape = #shape;
176 const SCHEMA: ::ic_wire::schema::Schema = #schema;
177 }
178 })
179}