anchor_attribute_account/
lib.rs1extern crate proc_macro;
2
3use {
4 anchor_syn::{codegen::program::common::gen_discriminator, Overrides},
5 quote::{quote, ToTokens},
6 syn::{
7 parenthesized,
8 parse::{Parse, ParseStream},
9 parse_macro_input,
10 token::Paren,
11 Ident, LitStr, Token,
12 },
13};
14
15mod id;
16
17#[cfg(feature = "lazy-account")]
18mod lazy;
19
20#[proc_macro_attribute]
99pub fn account(
100 args: proc_macro::TokenStream,
101 input: proc_macro::TokenStream,
102) -> proc_macro::TokenStream {
103 let args = parse_macro_input!(args as AccountArgs);
104 let namespace = args.namespace.unwrap_or_default();
105 let is_zero_copy = args.zero_copy.is_some();
106 let unsafe_bytemuck = args.zero_copy.unwrap_or_default();
107
108 let account_strct = parse_macro_input!(input as syn::ItemStruct);
109 let account_name = &account_strct.ident;
110 let account_name_str = account_name.to_string();
111 let (impl_gen, type_gen, where_clause) = account_strct.generics.split_for_impl();
112
113 let discriminator = args
114 .overrides
115 .and_then(|ov| ov.discriminator)
116 .unwrap_or_else(|| {
117 let namespace = if namespace.is_empty() {
119 "account"
120 } else {
121 &namespace
122 };
123
124 gen_discriminator(namespace, account_name)
125 });
126 let disc = if account_strct.generics.lt_token.is_some() {
127 quote! { #account_name::#type_gen::DISCRIMINATOR }
128 } else {
129 quote! { #account_name::DISCRIMINATOR }
130 };
131
132 let owner_impl = {
133 if namespace.is_empty() {
134 quote! {
135 #[automatically_derived]
136 impl #impl_gen anchor_lang::Owner for #account_name #type_gen #where_clause {
137 fn owner() -> Pubkey {
138 #[cfg(not(doctest))]
140 { crate::ID }
141 #[cfg(doctest)]
142 { ID }
143 }
144 }
145 }
146 } else {
147 quote! {}
148 }
149 };
150
151 let unsafe_bytemuck_impl = {
152 if unsafe_bytemuck {
153 quote! {
154 #[automatically_derived]
155 unsafe impl #impl_gen anchor_lang::__private::bytemuck::Pod for #account_name #type_gen #where_clause {}
156 #[automatically_derived]
157 unsafe impl #impl_gen anchor_lang::__private::bytemuck::Zeroable for #account_name #type_gen #where_clause {}
158 }
159 } else {
160 quote! {}
161 }
162 };
163
164 let bytemuck_derives = {
165 if !unsafe_bytemuck {
166 quote! {
167 #[zero_copy]
168 }
169 } else {
170 quote! {
171 #[zero_copy(unsafe)]
172 }
173 }
174 };
175
176 proc_macro::TokenStream::from({
177 if is_zero_copy {
178 quote! {
179 #bytemuck_derives
180 #account_strct
181
182 #unsafe_bytemuck_impl
183
184 #[automatically_derived]
185 impl #impl_gen anchor_lang::ZeroCopy for #account_name #type_gen #where_clause {}
186
187 #[automatically_derived]
188 impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
189 const DISCRIMINATOR: &'static [u8] = #discriminator;
190 }
191
192 #[automatically_derived]
195 impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
196 fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
197 if buf.len() < #disc.len() {
198 return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
199 }
200 let given_disc = &buf[..#disc.len()];
201 if #disc != given_disc {
202 return Err(anchor_lang::error!(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch).with_account_name(#account_name_str));
203 }
204 Self::try_deserialize_unchecked(buf)
205 }
206
207 fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
208 let data: &[u8] = &buf[#disc.len()..];
209 Ok(anchor_lang::__private::bytemuck::pod_read_unaligned(data))
210 }
211 }
212
213 #owner_impl
214 }
215 } else {
216 let lazy = {
217 #[cfg(feature = "lazy-account")]
218 match namespace.is_empty().then(|| lazy::gen_lazy(&account_strct)) {
219 Some(Ok(lazy)) => lazy,
220 _ => Default::default(),
223 }
224 #[cfg(not(feature = "lazy-account"))]
225 proc_macro2::TokenStream::default()
226 };
227 quote! {
228 #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
229 #account_strct
230
231 #[automatically_derived]
232 impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause {
233 fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> anchor_lang::Result<()> {
234 if writer.write_all(#disc).is_err() {
235 return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
236 }
237
238 if AnchorSerialize::serialize(self, writer).is_err() {
239 return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
240 }
241 Ok(())
242 }
243 }
244
245 #[automatically_derived]
246 impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
247 fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
248 if buf.len() < #disc.len() {
249 return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
250 }
251 let given_disc = &buf[..#disc.len()];
252 if #disc != given_disc {
253 return Err(anchor_lang::error!(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch).with_account_name(#account_name_str));
254 }
255 Self::try_deserialize_unchecked(buf)
256 }
257
258 fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
259 let mut data: &[u8] = &buf[#disc.len()..];
260 AnchorDeserialize::deserialize(&mut data)
261 .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
262 }
263 }
264
265 #[automatically_derived]
266 impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
267 const DISCRIMINATOR: &'static [u8] = #discriminator;
268 }
269
270 #owner_impl
271
272 #lazy
273 }
274 }
275 })
276}
277
278#[derive(Debug, Default)]
279struct AccountArgs {
280 zero_copy: Option<bool>,
282 namespace: Option<String>,
284 overrides: Option<Overrides>,
286}
287
288impl Parse for AccountArgs {
289 fn parse(input: ParseStream) -> syn::Result<Self> {
290 let mut parsed = Self::default();
291 let args = input.parse_terminated(AccountArg::parse, Token![,])?;
292 for arg in args {
293 match arg {
294 AccountArg::ZeroCopy { is_unsafe } => {
295 parsed.zero_copy.replace(is_unsafe);
296 }
297 AccountArg::Namespace(ns) => {
298 parsed.namespace.replace(ns);
299 }
300 AccountArg::Overrides(ov) => {
301 parsed.overrides.replace(ov);
302 }
303 }
304 }
305
306 Ok(parsed)
307 }
308}
309
310enum AccountArg {
311 ZeroCopy { is_unsafe: bool },
312 Namespace(String),
313 Overrides(Overrides),
314}
315
316impl Parse for AccountArg {
317 fn parse(input: ParseStream) -> syn::Result<Self> {
318 if let Ok(ns) = input.parse::<LitStr>() {
320 return Ok(Self::Namespace(
321 ns.to_token_stream().to_string().replace('\"', ""),
322 ));
323 }
324
325 if input
327 .fork()
328 .parse::<Ident>()
329 .is_ok_and(|ident| ident == "zero_copy")
330 {
331 input.parse::<Ident>()?;
332 let is_unsafe = if input.peek(Paren) {
333 let content;
334 parenthesized!(content in input);
335 let content = content.parse::<proc_macro2::TokenStream>()?;
336 if content.to_string().as_str().trim() != "unsafe" {
337 return Err(syn::Error::new(
338 syn::spanned::Spanned::span(&content),
339 "Expected `unsafe`",
340 ));
341 }
342 true
343 } else {
344 false
345 };
346
347 return Ok(Self::ZeroCopy { is_unsafe });
348 }
349
350 input.parse::<Overrides>().map(Self::Overrides)
354 }
355}
356
357#[proc_macro_derive(ZeroCopyAccessor, attributes(accessor))]
358pub fn derive_zero_copy_accessor(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
359 let account_strct = parse_macro_input!(item as syn::ItemStruct);
360 let account_name = &account_strct.ident;
361 let (impl_gen, ty_gen, where_clause) = account_strct.generics.split_for_impl();
362
363 let fields = match &account_strct.fields {
364 syn::Fields::Named(n) => n,
365 _ => {
366 return syn::Error::new_spanned(
367 &account_strct.ident,
368 "#[derive(ZeroCopyAccessor)] requires a struct with named fields",
369 )
370 .into_compile_error()
371 .into()
372 }
373 };
374 let methods: Vec<proc_macro2::TokenStream> = fields
375 .named
376 .iter()
377 .filter_map(|field: &syn::Field| {
378 field
379 .attrs
380 .iter()
381 .find(|attr| anchor_syn::parser::tts_to_string(attr.path()) == "accessor")
382 .map(|attr| {
383 let tokens = match &attr.meta {
384 syn::Meta::List(list) => list.tokens.clone(),
385 _ => {
386 return syn::Error::new_spanned(
387 attr,
388 "`#[accessor]` requires a type argument, e.g `#[accessor(MyType)]`",
389 )
390 .into_compile_error();
391 }
392 };
393 let accessor_ty = match tokens.into_iter().next() {
394 Some(token) => token,
395 None => {
396 return syn::Error::new_spanned(
397 attr,
398 "`#[accessor]` requires a type inside the parentheses e.g \
399 `#[accessor(MyType)]`",
400 )
401 .into_compile_error()
402 }
403 };
404
405 #[allow(
406 clippy::unwrap_used,
407 reason = "accessor fields always have idents (named struct fields)"
408 )]
409 let field_name = field.ident.as_ref().unwrap();
410 #[allow(
411 clippy::unwrap_used,
412 reason = "get_<field_name> formed from a valid Rust identifier is always \
413 valid TokenStream"
414 )]
415 let get_field: proc_macro2::TokenStream =
416 format!("get_{field_name}").parse().unwrap();
417 #[allow(
418 clippy::unwrap_used,
419 reason = "set_<field_name> formed from a valid Rust identifier is always \
420 valid TokenStream"
421 )]
422 let set_field: proc_macro2::TokenStream =
423 format!("set_{field_name}").parse().unwrap();
424
425 quote! {
426 pub fn #get_field(&self) -> #accessor_ty {
427 anchor_lang::__private::ZeroCopyAccessor::get(&self.#field_name)
428 }
429 pub fn #set_field(&mut self, input: &#accessor_ty) {
430 self.#field_name = anchor_lang::__private::ZeroCopyAccessor::set(input);
431 }
432 }
433 })
434 })
435 .collect();
436 proc_macro::TokenStream::from(quote! {
437 #[automatically_derived]
438 impl #impl_gen #account_name #ty_gen #where_clause {
439 #(#methods)*
440 }
441 })
442}
443
444#[proc_macro_attribute]
457pub fn zero_copy(
458 args: proc_macro::TokenStream,
459 item: proc_macro::TokenStream,
460) -> proc_macro::TokenStream {
461 let mut is_unsafe = false;
462 for arg in args.into_iter() {
463 match arg {
464 proc_macro::TokenTree::Ident(ident) => {
465 if ident.to_string() == "unsafe" {
466 is_unsafe = true;
474 } else {
475 return syn::Error::new(
476 proc_macro2::Span::from(ident.span()),
477 "expected `unsafe`, e.g `#[zero_copy(unsafe)]`",
478 )
479 .into_compile_error()
480 .into();
481 }
482 }
483 _ => {
484 return syn::Error::new(
485 proc_macro2::Span::from(arg.span()),
486 "expected `unsafe`, e.g `#[zero_copy(unsafe)]`",
487 )
488 .into_compile_error()
489 .into();
490 }
491 }
492 }
493
494 let account_strct = parse_macro_input!(item as syn::ItemStruct);
495
496 let attr = account_strct
499 .attrs
500 .iter()
501 .find(|attr| anchor_syn::parser::tts_to_string(attr.path()) == "repr");
502
503 let repr = match attr {
504 Some(_attr) => quote! {},
506 None => {
507 if is_unsafe {
508 quote! {#[repr(Rust, packed)]}
509 } else {
510 quote! {#[repr(C)]}
511 }
512 }
513 };
514
515 let mut has_pod_attr = false;
516 let mut has_zeroable_attr = false;
517 for attr in account_strct.attrs.iter() {
518 if !attr.path().is_ident("derive") {
519 continue;
520 }
521 if let syn::Meta::List(list) = &attr.meta {
522 let tokens_str = list.tokens.to_string();
523 if tokens_str.contains("bytemuck :: Pod") {
524 has_pod_attr = true;
525 }
526 if tokens_str.contains("bytemuck :: Zeroable") {
527 has_zeroable_attr = true;
528 }
529 }
530 }
531
532 let pod = if has_pod_attr || is_unsafe {
537 quote! {}
538 } else {
539 quote! {#[derive(::bytemuck::Pod)]}
540 };
541 let zeroable = if has_zeroable_attr || is_unsafe {
542 quote! {}
543 } else {
544 quote! {#[derive(::bytemuck::Zeroable)]}
545 };
546
547 let ret = quote! {
548 #[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
549 #repr
550 #pod
551 #zeroable
552 #account_strct
553 };
554
555 #[cfg(feature = "idl-build")]
556 {
557 let derive_unsafe = if is_unsafe {
558 quote! { #[derive(bytemuck::Unsafe)] }
560 } else {
561 quote! {}
562 };
563
564 let zc_struct = syn::parse_quote! {
565 #derive_unsafe
566 #ret
567 };
568 let idl_build_impl = anchor_syn::idl::impl_idl_build_struct(&zc_struct);
569 return proc_macro::TokenStream::from(quote! {
570 #ret
571 #idl_build_impl
572 });
573 }
574
575 #[allow(unreachable_code)]
576 proc_macro::TokenStream::from(ret)
577}
578
579#[proc_macro]
583pub fn pubkey(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
584 let pk = parse_macro_input!(input as id::Pubkey);
585 proc_macro::TokenStream::from(quote! {#pk})
586}
587
588#[proc_macro]
591pub fn declare_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
592 #[cfg(feature = "idl-build")]
593 let address = input.clone().to_string();
594
595 let id = parse_macro_input!(input as id::Id);
596 let ret = quote! { #id };
597
598 #[cfg(feature = "idl-build")]
599 {
600 let idl_print = anchor_syn::idl::gen_idl_print_fn_address(address);
601 return proc_macro::TokenStream::from(quote! {
602 #ret
603 #idl_print
604 });
605 }
606
607 #[allow(unreachable_code)]
608 proc_macro::TokenStream::from(ret)
609}