1extern crate proc_macro;
6
7use {
8 proc_macro::TokenStream,
9 proc_macro2::Span,
10 quote::{quote, ToTokens},
11 syn::{
12 bracketed,
13 parse::{Parse, ParseStream, Result},
14 parse_macro_input,
15 punctuated::Punctuated,
16 token::Bracket,
17 Expr, Ident, LitByte, LitStr, Token,
18 },
19};
20
21fn parse_id(
22 input: ParseStream,
23 pubkey_type: proc_macro2::TokenStream,
24) -> Result<proc_macro2::TokenStream> {
25 let id = if input.peek(syn::LitStr) {
26 let id_literal: LitStr = input.parse()?;
27 parse_pubkey(&id_literal, &pubkey_type)?
28 } else {
29 let expr: Expr = input.parse()?;
30 quote! { #expr }
31 };
32
33 if !input.is_empty() {
34 let stream: proc_macro2::TokenStream = input.parse()?;
35 return Err(syn::Error::new_spanned(stream, "unexpected token"));
36 }
37 Ok(id)
38}
39
40fn id_to_tokens(
41 id: &proc_macro2::TokenStream,
42 pubkey_type: proc_macro2::TokenStream,
43 tokens: &mut proc_macro2::TokenStream,
44) {
45 tokens.extend(quote! {
46 pub const ID: #pubkey_type = #id;
48
49 pub fn check_id(id: &#pubkey_type) -> bool {
53 id == &ID
54 }
55
56 pub const fn id() -> #pubkey_type {
58 ID
59 }
60
61 #[cfg(test)]
62 #[test]
63 fn test_id() {
64 assert!(check_id(&id()));
65 }
66 });
67}
68
69fn deprecated_id_to_tokens(
70 id: &proc_macro2::TokenStream,
71 pubkey_type: proc_macro2::TokenStream,
72 tokens: &mut proc_macro2::TokenStream,
73) {
74 tokens.extend(quote! {
75 pub static ID: #pubkey_type = #id;
77
78 #[deprecated()]
80 pub fn check_id(id: &#pubkey_type) -> bool {
81 id == &ID
82 }
83
84 #[deprecated()]
86 pub fn id() -> #pubkey_type {
87 ID
88 }
89
90 #[cfg(test)]
91 #[test]
92 #[allow(deprecated)]
93 fn test_id() {
94 assert!(check_id(&id()));
95 }
96 });
97}
98
99struct Id(proc_macro2::TokenStream);
100
101impl Parse for Id {
102 fn parse(input: ParseStream) -> Result<Self> {
103 parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
104 }
105}
106
107impl ToTokens for Id {
108 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
109 id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens)
110 }
111}
112
113struct IdDeprecated(proc_macro2::TokenStream);
114
115impl Parse for IdDeprecated {
116 fn parse(input: ParseStream) -> Result<Self> {
117 parse_id(input, quote! { ::solana_sdk::pubkey::Pubkey }).map(Self)
118 }
119}
120
121impl ToTokens for IdDeprecated {
122 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
123 deprecated_id_to_tokens(&self.0, quote! { ::solana_sdk::pubkey::Pubkey }, tokens)
124 }
125}
126
127#[proc_macro]
128pub fn declare_id(input: TokenStream) -> TokenStream {
129 let id = parse_macro_input!(input as Id);
130 TokenStream::from(quote! {#id})
131}
132
133#[proc_macro]
134pub fn declare_deprecated_id(input: TokenStream) -> TokenStream {
135 let id = parse_macro_input!(input as IdDeprecated);
136 TokenStream::from(quote! {#id})
137}
138
139fn parse_pubkey(
140 id_literal: &LitStr,
141 pubkey_type: &proc_macro2::TokenStream,
142) -> Result<proc_macro2::TokenStream> {
143 let id_vec = bs58::decode(id_literal.value())
144 .into_vec()
145 .map_err(|_| syn::Error::new_spanned(id_literal, "failed to decode base58 string"))?;
146 let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| {
147 syn::Error::new_spanned(
148 id_literal,
149 format!("pubkey array is not 32 bytes long: len={}", id_vec.len()),
150 )
151 })?;
152 let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site()));
153 Ok(quote! {
154 #pubkey_type::new_from_array(
155 [#(#bytes,)*]
156 )
157 })
158}
159
160struct Pubkeys {
161 method: Ident,
162 num: usize,
163 pubkeys: proc_macro2::TokenStream,
164}
165impl Parse for Pubkeys {
166 fn parse(input: ParseStream) -> Result<Self> {
167 let pubkey_type = quote! {
168 ::solana_sdk::pubkey::Pubkey
169 };
170
171 let method = input.parse()?;
172 let _comma: Token![,] = input.parse()?;
173 let (num, pubkeys) = if input.peek(syn::LitStr) {
174 let id_literal: LitStr = input.parse()?;
175 (1, parse_pubkey(&id_literal, &pubkey_type)?)
176 } else if input.peek(Bracket) {
177 let pubkey_strings;
178 bracketed!(pubkey_strings in input);
179 let punctuated: Punctuated<LitStr, Token![,]> =
180 Punctuated::parse_terminated(&pubkey_strings)?;
181 let mut pubkeys: Punctuated<proc_macro2::TokenStream, Token![,]> = Punctuated::new();
182 for string in punctuated.iter() {
183 pubkeys.push(parse_pubkey(string, &pubkey_type)?);
184 }
185 (pubkeys.len(), quote! {#pubkeys})
186 } else {
187 let stream: proc_macro2::TokenStream = input.parse()?;
188 return Err(syn::Error::new_spanned(stream, "unexpected token"));
189 };
190
191 Ok(Pubkeys {
192 method,
193 num,
194 pubkeys,
195 })
196 }
197}
198
199impl ToTokens for Pubkeys {
200 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
201 let Pubkeys {
202 method,
203 num,
204 pubkeys,
205 } = self;
206
207 let pubkey_type = quote! {
208 ::solana_sdk::pubkey::Pubkey
209 };
210 if *num == 1 {
211 tokens.extend(quote! {
212 pub fn #method() -> #pubkey_type {
213 #pubkeys
214 }
215 });
216 } else {
217 tokens.extend(quote! {
218 pub fn #method() -> ::std::vec::Vec<#pubkey_type> {
219 vec![#pubkeys]
220 }
221 });
222 }
223 }
224}
225
226#[proc_macro]
227pub fn pubkeys(input: TokenStream) -> TokenStream {
228 let pubkeys = parse_macro_input!(input as Pubkeys);
229 TokenStream::from(quote! {#pubkeys})
230}
231
232#[proc_macro_derive(CloneZeroed)]
235pub fn derive_clone_zeroed(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
236 match parse_macro_input!(input as syn::Item) {
237 syn::Item::Struct(item_struct) => {
238 let clone_statements = match item_struct.fields {
239 syn::Fields::Named(ref fields) => fields.named.iter().map(|f| {
240 let name = &f.ident;
241 quote! {
242 core::ptr::addr_of_mut!((*ptr).#name).write(self.#name);
243 }
244 }),
245 _ => unimplemented!(),
246 };
247 let name = &item_struct.ident;
248 quote! {
249 impl Clone for #name {
250 fn clone(&self) -> Self {
255 let mut value = core::mem::MaybeUninit::<Self>::uninit();
256 unsafe {
257 core::ptr::write_bytes(&mut value, 0, 1);
258 let ptr = value.as_mut_ptr();
259 #(#clone_statements)*
260 value.assume_init()
261 }
262 }
263 }
264 }
265 }
266 _ => unimplemented!(),
267 }
268 .into()
269}