1use proc_macro::TokenStream;
3use quote::{format_ident, quote};
4use std::collections::BTreeSet;
5use syn::{Data, DeriveInput, Fields, parse_macro_input};
6
7#[proc_macro_derive(Wire, attributes(wire))]
9pub fn derive_wire(input: TokenStream) -> TokenStream {
10 expand(parse_macro_input!(input as DeriveInput))
11 .unwrap_or_else(syn::Error::into_compile_error)
12 .into()
13}
14
15fn identity(attrs: &[syn::Attribute]) -> syn::Result<u32> {
16 let mut id = None;
17 for attr in attrs.iter().filter(|a| a.path().is_ident("wire")) {
18 attr.parse_nested_meta(|meta| {
19 if !meta.path.is_ident("id") || id.is_some() {
20 return Err(meta.error("expected one wire id"));
21 }
22 id = Some(meta.value()?.parse::<syn::LitInt>()?.base10_parse()?);
23 Ok(())
24 })?;
25 }
26 id.ok_or_else(|| {
27 syn::Error::new(
28 proc_macro2::Span::call_site(),
29 "explicit #[wire(id = N)] required",
30 )
31 })
32}
33
34fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
35 if !input.generics.params.is_empty() || input.generics.where_clause.is_some() {
36 return Err(syn::Error::new_spanned(
37 input.generics,
38 "Wire requires concrete contracts",
39 ));
40 }
41 if input.attrs.iter().any(|a| a.path().is_ident("wire")) {
42 return Err(syn::Error::new_spanned(
43 &input.ident,
44 "wire ids belong on fields or variants",
45 ));
46 }
47 let name = input.ident;
48 let mut ids = BTreeSet::new();
49 let (serialize, deserialize, shape, schema) = match input.data {
50 Data::Struct(data) => {
51 let Fields::Named(fields) = data.fields else {
52 return Err(syn::Error::new_spanned(
53 name,
54 "use a named-field record (empty records are allowed)",
55 ));
56 };
57 if fields.named.len() > 16 {
58 return Err(syn::Error::new_spanned(
59 name,
60 "at most 16 fields; group related fields in a nested record",
61 ));
62 }
63 let mut fields = fields
64 .named
65 .into_iter()
66 .map(|field| {
67 let id = identity(&field.attrs)?;
68 if !ids.insert(id) {
69 return Err(syn::Error::new_spanned(&field, "duplicate wire id"));
70 }
71 Ok((id, field.ident.unwrap(), field.ty))
72 })
73 .collect::<syn::Result<Vec<_>>>()?;
74 fields.sort_by_key(|f| f.0);
75 let names: Vec<_> = fields.iter().map(|f| &f.1).collect();
76 let types: Vec<_> = fields.iter().map(|f| &f.2).collect();
77 let labels: Vec<_> = names
78 .iter()
79 .map(|name| name.to_string().trim_start_matches("r#").to_owned())
80 .collect();
81 let field_ids: Vec<_> = fields.iter().map(|f| f.0).collect();
82 (
83 quote! { ::ic_wire::__private::serde::Serialize::serialize(&(#(&self.#names,)*), serializer) },
84 quote! {
85 let (#(#names,)*): (#(#types,)*) = ::ic_wire::__private::serde::Deserialize::deserialize(deserializer)?;
86 Ok(Self { #(#names,)* })
87 },
88 quote! { ::ic_wire::Shape::record(&[#(<#types as ::ic_wire::Wire>::SHAPE,)*]) },
89 quote! { ::ic_wire::schema::Schema::Record(&[#(::ic_wire::schema::Field { name: #labels, id: #field_ids, value: &<#types as ::ic_wire::Wire>::SCHEMA },)*]) },
90 )
91 }
92 Data::Enum(data) => {
93 if data.variants.is_empty() {
94 return Err(syn::Error::new_spanned(name, "empty enum is unsupported"));
95 }
96 let visitor = format_ident!("__IcWireVisitorFor{}", name);
97 let mut serializers = Vec::new();
98 let mut decoders = Vec::new();
99 let mut shapes = Vec::new();
100 let mut variants = Vec::new();
101 for variant in data.variants {
102 let id = identity(&variant.attrs)?;
103 if !ids.insert(id) {
104 return Err(syn::Error::new_spanned(&variant, "duplicate wire id"));
105 }
106 if variant.discriminant.is_some() {
107 return Err(syn::Error::new_spanned(
108 &variant,
109 "use wire id, not Rust discriminants",
110 ));
111 }
112 let v = variant.ident;
113 let label = v.to_string().trim_start_matches("r#").to_owned();
114 match variant.fields {
115 Fields::Unit => {
116 serializers.push(quote! { Self::#v => ::ic_wire::__private::serde::Serialize::serialize(&(#id, ()), serializer) });
117 decoders.push(quote! { #id => { let _: () = seq.next_element()?.ok_or_else(|| ::ic_wire::__private::serde::de::Error::custom("missing variant payload"))?; Ok(#name::#v) } });
118 shapes.push(quote! { <() as ::ic_wire::Wire>::SHAPE });
119 variants.push(quote! { ::ic_wire::schema::Variant { name: #label, id: #id, value: &<() as ::ic_wire::Wire>::SCHEMA } });
120 }
121 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
122 let field = &fields.unnamed[0];
123 if field.attrs.iter().any(|a| a.path().is_ident("wire")) {
124 return Err(syn::Error::new_spanned(
125 field,
126 "variant payload inherits its variant id",
127 ));
128 }
129 let ty = &field.ty;
130 serializers.push(quote! { Self::#v(value) => ::ic_wire::__private::serde::Serialize::serialize(&(#id, value), serializer) });
131 decoders.push(quote! { #id => Ok(#name::#v(seq.next_element::<#ty>()?.ok_or_else(|| ::ic_wire::__private::serde::de::Error::custom("missing variant payload"))?)) });
132 shapes.push(quote! { <#ty as ::ic_wire::Wire>::SHAPE });
133 variants.push(quote! { ::ic_wire::schema::Variant { name: #label, id: #id, value: &<#ty as ::ic_wire::Wire>::SCHEMA } });
134 }
135 _ => {
136 return Err(syn::Error::new_spanned(
137 v,
138 "use unit variants or one named-record payload",
139 ));
140 }
141 }
142 }
143 (
144 quote! { match self { #(#serializers,)* } },
145 quote! {
146 struct #visitor;
147 impl<'de> ::ic_wire::__private::serde::de::Visitor<'de> for #visitor {
148 type Value = #name;
149 fn expecting(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { f.write_str("explicit wire variant") }
150 fn visit_seq<A: ::ic_wire::__private::serde::de::SeqAccess<'de>>(self, mut seq: A) -> ::core::result::Result<Self::Value, A::Error> {
151 let tag: u32 = seq.next_element()?.ok_or_else(|| ::ic_wire::__private::serde::de::Error::custom("missing variant id"))?;
152 match tag { #(#decoders,)* _ => Err(::ic_wire::__private::serde::de::Error::custom("unknown variant id")) }
153 }
154 }
155 deserializer.deserialize_tuple(2, #visitor)
156 },
157 quote! { ::ic_wire::Shape::choice(5, &[#(#shapes,)*]) },
158 quote! { ::ic_wire::schema::Schema::Enum(&[#(#variants,)*]) },
159 )
160 }
161 Data::Union(_) => return Err(syn::Error::new_spanned(name, "unions are unsupported")),
162 };
163 Ok(quote! {
164 impl ::ic_wire::__private::serde::Serialize for #name {
165 fn serialize<S: ::ic_wire::__private::serde::Serializer>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error> { #serialize }
166 }
167 impl<'de> ::ic_wire::__private::serde::Deserialize<'de> for #name {
168 fn deserialize<D: ::ic_wire::__private::serde::Deserializer<'de>>(deserializer: D) -> ::core::result::Result<Self, D::Error> { #deserialize }
169 }
170 impl ::ic_wire::Wire for #name {
171 const SHAPE: ::ic_wire::Shape = #shape;
172 const SCHEMA: ::ic_wire::schema::Schema = #schema;
173 }
174 })
175}
176
177#[cfg(feature = "adapter-fixture")]
178mod adapter_fixture;
179
180#[cfg(feature = "adapter-fixture")]
182#[proc_macro_attribute]
183pub fn query(args: TokenStream, item: TokenStream) -> TokenStream {
184 adapter_fixture::expand(args.into(), item.into(), false)
185 .unwrap_or_else(syn::Error::into_compile_error)
186 .into()
187}
188
189#[cfg(feature = "adapter-fixture")]
191#[proc_macro_attribute]
192pub fn update(args: TokenStream, item: TokenStream) -> TokenStream {
193 adapter_fixture::expand(args.into(), item.into(), true)
194 .unwrap_or_else(syn::Error::into_compile_error)
195 .into()
196}