Skip to main content

rialo_sol_derive_serde/
lib.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Defines the [`SolSerialize`] and [`SolDeserialize`] derive macros.
5// These emit a `BorshSerialize`/`BorshDeserialize` implementation for the given type,
6// as well as emitting IDL type information when the `idl-build` feature is enabled.
7
8#[allow(unused_extern_crates)]
9extern crate proc_macro;
10
11#[cfg(feature = "lazy-account")]
12mod lazy;
13
14use proc_macro::TokenStream;
15use proc_macro2::{Span, TokenStream as TokenStream2};
16use proc_macro_crate::FoundCrate;
17use quote::quote;
18use syn::Ident;
19
20fn gen_borsh_serialize(input: TokenStream) -> TokenStream2 {
21    let input = TokenStream2::from(input);
22    let attrs = helper_attrs("BorshSerialize");
23    quote! {
24        #attrs
25        #input
26    }
27}
28
29#[proc_macro_derive(SolSerialize)]
30pub fn sol_serialize(input: TokenStream) -> TokenStream {
31    #[cfg(not(feature = "idl-build"))]
32    let ret = gen_borsh_serialize(input);
33    #[cfg(feature = "idl-build")]
34    let ret = gen_borsh_serialize(input.clone());
35
36    #[cfg(feature = "idl-build")]
37    {
38        use quote::quote;
39        use rialo_sol_syn::idl::*;
40        use syn::Item;
41
42        let idl_build_impl = match syn::parse(input).unwrap() {
43            Item::Struct(item) => impl_idl_build_struct(&item),
44            Item::Enum(item) => impl_idl_build_enum(&item),
45            Item::Union(item) => impl_idl_build_union(&item),
46            // Derive macros can only be defined on structs, enums, and unions.
47            _ => unreachable!(),
48        };
49
50        return TokenStream::from(quote! {
51            #ret
52            #idl_build_impl
53        });
54    };
55
56    #[allow(unreachable_code)]
57    TokenStream::from(ret)
58}
59
60fn gen_borsh_deserialize(input: TokenStream) -> TokenStream2 {
61    let input = TokenStream2::from(input);
62    let attrs = helper_attrs("BorshDeserialize");
63    quote! {
64        #attrs
65        #input
66    }
67}
68
69#[proc_macro_derive(SolDeserialize)]
70pub fn sol_deserialize(input: TokenStream) -> TokenStream {
71    #[cfg(feature = "lazy-account")]
72    {
73        let deser = gen_borsh_deserialize(input.clone());
74        let lazy = lazy::gen_lazy(input).unwrap_or_else(|e| e.to_compile_error());
75        quote::quote! {
76            #deser
77            #lazy
78        }
79        .into()
80    }
81    #[cfg(not(feature = "lazy-account"))]
82    gen_borsh_deserialize(input).into()
83}
84
85fn helper_attrs(mac: &str) -> TokenStream2 {
86    // We need to emit the original borsh deserialization macros on our type,
87    // but derive macros can't emit other derives. To get around this, we use a hack:
88    // 1. Define an `__erase` attribute macro which deletes the item it is applied to
89    // 2. Emit a call to the derive, followed by a copy of the input struct with #[__erase] applied
90    // 3. This results in the trait implementations being produced, but the duplicate type definition
91    //    being deleted
92
93    let mac_path = Ident::new(mac, Span::call_site());
94    let sol_lang = proc_macro_crate::crate_name("rialo-sol-lang")
95        .expect("`rialo-sol-derive-serde` must be used via `rialo-sol-lang`");
96
97    let sol_lang_path = Ident::new(
98        match &sol_lang {
99            FoundCrate::Itself => "crate",
100            FoundCrate::Name(cr) => cr.as_str(),
101        },
102        Span::call_site(),
103    );
104    let borsh_path = quote! { #sol_lang_path::prelude::borsh };
105    let borsh_path_str = borsh_path.to_string();
106
107    quote! {
108        #[derive(#borsh_path::#mac_path)]
109        // Borsh derives used in a re-export require providing the path to `borsh`
110        #[borsh(crate = #borsh_path_str)]
111        #[#sol_lang_path::__erase]
112    }
113}
114
115/// Deletes the item it is applied to. Implementation detail and not part of public API.
116#[doc(hidden)]
117#[proc_macro_attribute]
118pub fn __erase(_: TokenStream, _: TokenStream) -> TokenStream {
119    TokenStream::new()
120}
121
122#[cfg(feature = "lazy-account")]
123#[proc_macro_derive(Lazy)]
124pub fn lazy_derive(input: TokenStream) -> TokenStream {
125    lazy::gen_lazy(input)
126        .unwrap_or_else(|e| e.to_compile_error())
127        .into()
128}