Skip to main content

anchor_derive_serde/
lib.rs

1//! Defines the [`AnchorSerialize`] and [`AnchorDeserialize`] derive macros
2//! These emit a `BorshSerialize`/`BorshDeserialize` implementation for the given type,
3//! as well as emitting IDL type information when the `idl-build` feature is enabled.
4
5extern 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
20/// Only one item-level `#[borsh]` attribute may be present, and we apply our own borsh attribute.
21/// Remove any user-provided `#[borsh]` attributes to apply in our generated derive.
22fn 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/// Locate any `#[borsh]` attributes on struct/enum fields,
44/// which are currently unsupported with `lazy-account`.
45#[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        // `use_discriminant = false` is safe with `lazy-account` because it preserves
133        // borsh's default sequential tag encoding (0, 1, 2, ...) which Lazy's
134        // `size_of` relies on. `use_discriminant = true` would encode explicit
135        // discriminant values as the tag byte, breaking the Lazy match arms.
136        // Other item-level borsh attrs are not yet supported.
137        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/// Implements `borsh` deserialization for this structure, as well as implementing lazy
168/// deserialization if the `lazy-account` feature is enabled.
169/// `#[borsh(use_discriminant = false)]` is supported with `lazy-account`;
170/// `use_discriminant = true` and other `#[borsh]` attributes (e.g. `skip`) are not yet
171/// supported in conjunction with `lazy-account`.
172///
173/// ```
174/// # use anchor_derive_serde::AnchorDeserialize;
175/// #[derive(AnchorDeserialize)]
176/// #[borsh(use_discriminant = false)]
177/// pub enum Example {
178///     Foo = 1,
179///     Bar = 2,
180/// }
181/// ```
182///
183#[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/// # use anchor_derive_serde::AnchorDeserialize;
190/// #[derive(AnchorDeserialize)]
191/// pub struct Example {
192///     #[borsh(skip)]
193///     x: u8,
194/// }
195/// ```
196#[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    // We need to emit the original borsh deserialization macros on our type,
215    // but derive macros can't emit other derives. To get around this, we use a hack:
216    // 1. Define an `__erase` attribute macro which deletes the item it is applied to
217    // 2. Emit a call to the derive, followed by a copy of the input struct with #[__erase] applied
218    // 3. This results in the trait implementations being produced, but the duplicate type definition being deleted
219
220    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 derives used in a re-export require providing the path to `borsh`
244        #[borsh(crate = #borsh_path_str, #(#borsh_attrs),*)]
245        #[#anchor_path::__erase]
246    }
247}
248
249/// Deletes the item it is applied to. Implementation detail and not part of public API.
250#[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}