anchor_derive_serde/
lib.rs1extern crate proc_macro;
6
7#[cfg(feature = "lazy-account")]
8mod lazy;
9
10#[cfg(feature = "lazy-account")]
11use syn::spanned::Spanned;
12use {
13 proc_macro::TokenStream,
14 proc_macro2::{Span, TokenStream as TokenStream2},
15 proc_macro_crate::FoundCrate,
16 quote::quote,
17 syn::{parse_macro_input, DeriveInput, Ident},
18};
19
20fn extract_borsh_attrs(input: &mut DeriveInput) -> Vec<syn::Meta> {
23 input
24 .attrs
25 .extract_if(.., |attr| attr.path().is_ident("borsh"))
26 .filter_map(|attr| {
27 if let syn::Meta::List(list) = attr.meta {
28 Some(list.tokens)
29 } else {
30 None
31 }
32 })
33 .flat_map(|tokens| {
34 syn::parse::Parser::parse2(
35 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
36 tokens,
37 )
38 .unwrap_or_default()
39 })
40 .collect()
41}
42
43#[cfg(feature = "lazy-account")]
46fn find_field_borsh_attr(input: &DeriveInput) -> Option<&syn::Attribute> {
47 match &input.data {
48 syn::Data::Struct(data) => data
49 .fields
50 .iter()
51 .flat_map(|field| field.attrs.iter())
52 .find(|attr| attr.path().is_ident("borsh")),
53 syn::Data::Enum(data) => data
54 .variants
55 .iter()
56 .flat_map(|variant| variant.fields.iter())
57 .flat_map(|field| field.attrs.iter())
58 .find(|attr| attr.path().is_ident("borsh")),
59 syn::Data::Union(data) => data
60 .fields
61 .named
62 .iter()
63 .flat_map(|field| field.attrs.iter())
64 .find(|attr| attr.path().is_ident("borsh")),
65 }
66}
67
68fn gen_borsh_serialize(input: TokenStream) -> TokenStream {
69 let mut item = parse_macro_input!(input as DeriveInput);
70 let borsh_attrs = extract_borsh_attrs(&mut item);
71 let attrs = helper_attrs("BorshSerialize", borsh_attrs);
72 quote! {
73 #attrs
74 #item
75 }
76 .into()
77}
78
79#[proc_macro_derive(AnchorSerialize, attributes(borsh))]
80pub fn anchor_serialize(input: TokenStream) -> TokenStream {
81 #[cfg(not(feature = "idl-build"))]
82 let ret = gen_borsh_serialize(input);
83 #[cfg(feature = "idl-build")]
84 let ret = gen_borsh_serialize(input.clone());
85
86 #[cfg(feature = "idl-build")]
87 {
88 use {anchor_syn::idl::*, quote::quote, syn::Item};
89
90 #[allow(clippy::disallowed_macros)]
91 let idl_build_impl = match syn::parse(input) {
92 Err(e) => return e.to_compile_error().into(),
93 Ok(item) => match item {
94 Item::Struct(item) => impl_idl_build_struct(&item),
95 Item::Enum(item) => impl_idl_build_enum(&item),
96 Item::Union(item) => impl_idl_build_union(&item),
97 _ => syn::Error::new(
98 proc_macro2::Span::call_site(),
99 "AnchorSerialize can only be derived on structs, enums, and unions",
100 )
101 .to_compile_error(),
102 },
103 };
104
105 let ret = TokenStream2::from(ret);
106 return quote! {
107 #ret
108 #idl_build_impl
109 }
110 .into();
111 };
112
113 #[cfg(not(feature = "idl-build"))]
114 ret
115}
116
117fn gen_borsh_deserialize(input: TokenStream) -> TokenStream {
118 let mut item = parse_macro_input!(input as DeriveInput);
119 #[cfg(feature = "lazy-account")]
120 if let Some(attr) = find_field_borsh_attr(&item) {
121 return syn::Error::new(
122 attr.span(),
123 "`borsh` attributes are not currently supported with `lazy-account`",
124 )
125 .into_compile_error()
126 .into();
127 }
128
129 let borsh_attrs = extract_borsh_attrs(&mut item);
130 #[cfg(feature = "lazy-account")]
131 {
132 let unsupported = borsh_attrs.iter().find(|attr| {
138 !matches!(
139 attr,
140 syn::Meta::NameValue(nv)
141 if nv.path.is_ident("use_discriminant")
142 && matches!(
143 &nv.value,
144 syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Bool(b), .. })
145 if !b.value
146 )
147 )
148 });
149 if let Some(attr) = unsupported {
150 return syn::Error::new(
151 attr.span(),
152 "only `#[borsh(use_discriminant = false)]` is supported with `lazy-account`; \
153 `use_discriminant = true` and other `borsh` attributes are not yet supported",
154 )
155 .into_compile_error()
156 .into();
157 }
158 }
159 let attrs = helper_attrs("BorshDeserialize", borsh_attrs);
160 quote! {
161 #attrs
162 #item
163 }
164 .into()
165}
166
167#[cfg_attr(feature = "lazy-account", doc = "```compile_fail")]
184#[cfg_attr(
185 feature = "lazy-account",
186 doc = "// Will not compile with `lazy-account`"
187)]
188#[cfg_attr(not(feature = "lazy-account"), doc = "```")]
189#[proc_macro_derive(AnchorDeserialize, attributes(borsh))]
197pub fn anchor_deserialize(input: TokenStream) -> TokenStream {
198 #[cfg(feature = "lazy-account")]
199 {
200 let deser = TokenStream2::from(gen_borsh_deserialize(input.clone()));
201 let lazy = lazy::gen_lazy(input).unwrap_or_else(|e| e.to_compile_error());
202 quote! {
203 #deser
204 #lazy
205 }
206 .into()
207 }
208
209 #[cfg(not(feature = "lazy-account"))]
210 gen_borsh_deserialize(input)
211}
212
213fn helper_attrs(mac: &str, borsh_attrs: Vec<syn::Meta>) -> TokenStream2 {
214 let mac_path = Ident::new(mac, Span::call_site());
221 let anchor = match proc_macro_crate::crate_name("anchor-lang") {
222 Ok(found) => found,
223 Err(_) => {
224 return syn::Error::new(
225 Span::call_site(),
226 "`anchor-derive-serde` must be used via `anchor-lang`",
227 )
228 .into_compile_error()
229 }
230 };
231
232 let anchor_path = Ident::new(
233 match &anchor {
234 FoundCrate::Itself => "crate",
235 FoundCrate::Name(cr) => cr.as_str(),
236 },
237 Span::call_site(),
238 );
239 let borsh_path = quote! { #anchor_path::prelude::borsh };
240 let borsh_path_str = borsh_path.to_string();
241 quote! {
242 #[derive(#borsh_path::#mac_path)]
243 #[borsh(crate = #borsh_path_str, #(#borsh_attrs),*)]
245 #[#anchor_path::__erase]
246 }
247}
248
249#[doc(hidden)]
251#[proc_macro_attribute]
252pub fn __erase(_: TokenStream, _: TokenStream) -> TokenStream {
253 TokenStream::new()
254}
255
256#[cfg(feature = "lazy-account")]
257#[proc_macro_derive(Lazy)]
258pub fn lazy(input: TokenStream) -> TokenStream {
259 lazy::gen_lazy(input)
260 .unwrap_or_else(|e| e.to_compile_error())
261 .into()
262}