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, Meta, NestedMeta},
18};
19
20fn extract_borsh_attrs(input: &mut DeriveInput) -> Vec<NestedMeta> {
23 input
24 .attrs
25 .extract_if(.., |attr| attr.path.is_ident("borsh"))
26 .filter_map(|attr| {
27 if let Ok(Meta::List(list)) = attr.parse_meta() {
28 Some(list)
29 } else {
30 None
31 }
32 })
33 .flat_map(|list| list.nested)
34 .collect()
35}
36
37#[cfg(feature = "lazy-account")]
40fn find_field_borsh_attr(input: &DeriveInput) -> Option<&syn::Attribute> {
41 match &input.data {
42 syn::Data::Struct(data) => data
43 .fields
44 .iter()
45 .flat_map(|field| field.attrs.iter())
46 .find(|attr| attr.path.is_ident("borsh")),
47 syn::Data::Enum(data) => data
48 .variants
49 .iter()
50 .flat_map(|variant| variant.fields.iter())
51 .flat_map(|field| field.attrs.iter())
52 .find(|attr| attr.path.is_ident("borsh")),
53 syn::Data::Union(data) => data
54 .fields
55 .named
56 .iter()
57 .flat_map(|field| field.attrs.iter())
58 .find(|attr| attr.path.is_ident("borsh")),
59 }
60}
61
62fn gen_borsh_serialize(input: TokenStream) -> TokenStream {
63 let mut item = parse_macro_input!(input as DeriveInput);
64 let borsh_attrs = extract_borsh_attrs(&mut item);
65 let attrs = helper_attrs("BorshSerialize", borsh_attrs);
66 quote! {
67 #attrs
68 #item
69 }
70 .into()
71}
72
73#[proc_macro_derive(AnchorSerialize, attributes(borsh))]
74pub fn anchor_serialize(input: TokenStream) -> TokenStream {
75 #[cfg(not(feature = "idl-build"))]
76 let ret = gen_borsh_serialize(input);
77 #[cfg(feature = "idl-build")]
78 let ret = gen_borsh_serialize(input.clone());
79
80 #[cfg(feature = "idl-build")]
81 {
82 use {anchor_syn::idl::*, quote::quote, syn::Item};
83
84 let idl_build_impl = match syn::parse(input).unwrap() {
85 Item::Struct(item) => impl_idl_build_struct(&item),
86 Item::Enum(item) => impl_idl_build_enum(&item),
87 Item::Union(item) => impl_idl_build_union(&item),
88 _ => unreachable!(),
90 };
91
92 let ret = TokenStream2::from(ret);
93 return quote! {
94 #ret
95 #idl_build_impl
96 }
97 .into();
98 };
99
100 #[cfg(not(feature = "idl-build"))]
101 ret
102}
103
104fn gen_borsh_deserialize(input: TokenStream) -> TokenStream {
105 let mut item = parse_macro_input!(input as DeriveInput);
106 #[cfg(feature = "lazy-account")]
107 if let Some(attr) = find_field_borsh_attr(&item) {
108 return syn::Error::new(
109 attr.span(),
110 "`borsh` attributes are not currently supported with `lazy-account`",
111 )
112 .into_compile_error()
113 .into();
114 }
115
116 let borsh_attrs = extract_borsh_attrs(&mut item);
117 #[cfg(feature = "lazy-account")]
118 {
119 let unsupported = borsh_attrs.iter().find(|attr| {
125 !matches!(
126 attr,
127 NestedMeta::Meta(Meta::NameValue(nv))
128 if nv.path.is_ident("use_discriminant")
129 && matches!(&nv.lit, syn::Lit::Bool(b) if !b.value)
130 )
131 });
132 if let Some(attr) = unsupported {
133 return syn::Error::new(
134 attr.span(),
135 "only `#[borsh(use_discriminant = false)]` is supported with `lazy-account`; \
136 `use_discriminant = true` and other `borsh` attributes are not yet supported",
137 )
138 .into_compile_error()
139 .into();
140 }
141 }
142 let attrs = helper_attrs("BorshDeserialize", borsh_attrs);
143 quote! {
144 #attrs
145 #item
146 }
147 .into()
148}
149
150#[cfg_attr(feature = "lazy-account", doc = "```compile_fail")]
167#[cfg_attr(
168 feature = "lazy-account",
169 doc = "// Will not compile with `lazy-account`"
170)]
171#[cfg_attr(not(feature = "lazy-account"), doc = "```")]
172#[proc_macro_derive(AnchorDeserialize, attributes(borsh))]
180pub fn anchor_deserialize(input: TokenStream) -> TokenStream {
181 #[cfg(feature = "lazy-account")]
182 {
183 let deser = TokenStream2::from(gen_borsh_deserialize(input.clone()));
184 let lazy = lazy::gen_lazy(input).unwrap_or_else(|e| e.to_compile_error());
185 quote! {
186 #deser
187 #lazy
188 }
189 .into()
190 }
191
192 #[cfg(not(feature = "lazy-account"))]
193 gen_borsh_deserialize(input)
194}
195
196fn helper_attrs(mac: &str, borsh_attrs: Vec<NestedMeta>) -> TokenStream2 {
197 let mac_path = Ident::new(mac, Span::call_site());
204 let anchor = proc_macro_crate::crate_name("anchor-lang")
205 .expect("`anchor-derive-serde` must be used via `anchor-lang`");
206
207 let anchor_path = Ident::new(
208 match &anchor {
209 FoundCrate::Itself => "crate",
210 FoundCrate::Name(cr) => cr.as_str(),
211 },
212 Span::call_site(),
213 );
214 let borsh_path = quote! { #anchor_path::prelude::borsh };
215 let borsh_path_str = borsh_path.to_string();
216 quote! {
217 #[derive(#borsh_path::#mac_path)]
218 #[borsh(crate = #borsh_path_str, #(#borsh_attrs),*)]
220 #[#anchor_path::__erase]
221 }
222}
223
224#[doc(hidden)]
226#[proc_macro_attribute]
227pub fn __erase(_: TokenStream, _: TokenStream) -> TokenStream {
228 TokenStream::new()
229}
230
231#[cfg(feature = "lazy-account")]
232#[proc_macro_derive(Lazy)]
233pub fn lazy(input: TokenStream) -> TokenStream {
234 lazy::gen_lazy(input)
235 .unwrap_or_else(|e| e.to_compile_error())
236 .into()
237}