Skip to main content

anchor_syn/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3pub mod codegen;
4pub mod parser;
5
6#[cfg(feature = "idl-build")]
7pub mod idl;
8
9#[cfg(feature = "hash")]
10pub mod hash;
11#[cfg(not(feature = "hash"))]
12pub(crate) mod hash;
13
14use {
15    codegen::{accounts as accounts_codegen, program as program_codegen},
16    parser::{accounts as accounts_parser, program as program_parser},
17    proc_macro2::{Span, TokenStream},
18    quote::{quote, ToTokens},
19    std::{collections::HashMap, ops::Deref},
20    syn::{
21        ext::IdentExt,
22        parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult},
23        parse_quote,
24        punctuated::Punctuated,
25        spanned::Spanned,
26        token::Comma,
27        Attribute, Expr, ExprLit, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, Lit,
28        LitInt, PatType, Token, Type, TypePath,
29    },
30};
31
32/// Generic result type, only used when [`syn::Error`] doesn't make sense.
33#[cfg(feature = "idl-build")]
34pub(crate) type AnyResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
35
36#[derive(Debug)]
37pub struct Program {
38    pub ixs: Vec<Ix>,
39    pub name: Ident,
40    pub docs: Option<Vec<String>>,
41    pub program_mod: ItemMod,
42    pub fallback_fn: Option<FallbackFn>,
43    pub program_args: Option<ProgramArgs>,
44}
45
46impl Parse for Program {
47    fn parse(input: ParseStream) -> ParseResult<Self> {
48        let program_mod = <ItemMod as Parse>::parse(input)?;
49        program_parser::parse(program_mod)
50    }
51}
52
53impl From<&Program> for TokenStream {
54    fn from(program: &Program) -> Self {
55        program_codegen::generate(program)
56    }
57}
58
59impl ToTokens for Program {
60    fn to_tokens(&self, tokens: &mut TokenStream) {
61        tokens.extend::<TokenStream>(self.into());
62    }
63}
64
65#[derive(Debug, Default)]
66pub struct ProgramArgs {
67    legacy_idl: bool,
68}
69
70impl Parse for ProgramArgs {
71    fn parse(input: ParseStream) -> ParseResult<Self> {
72        let mut parsed = Self::default();
73        let args = input.parse_terminated(Ident::parse, Token![,])?;
74
75        for arg in args {
76            match arg.to_string().as_str() {
77                "legacy_idl" => {
78                    if parsed.legacy_idl {
79                        return Err(syn::Error::new(
80                            arg.span(),
81                            "Duplicate `legacy_idl` argument",
82                        ));
83                    }
84                    parsed.legacy_idl = true;
85                }
86                name => {
87                    return Err(syn::Error::new(
88                        arg.span(),
89                        format!("Invalid argument `{name}`. Expected one of: `legacy_idl`"),
90                    ));
91                }
92            }
93        }
94
95        Ok(parsed)
96    }
97}
98
99#[derive(Debug)]
100pub struct Ix {
101    pub raw_method: ItemFn,
102    pub ident: Ident,
103    pub docs: Option<Vec<String>>,
104    pub cfgs: Vec<Attribute>,
105    pub args: Vec<IxArg>,
106    pub returns: IxReturn,
107    // The ident for the struct deriving Accounts.
108    pub anchor_ident: Ident,
109    /// Overrides coming from the `#[instruction]` attribute
110    pub overrides: Option<Overrides>,
111}
112
113/// Common overrides for the `#[instruction]`, `#[account]` and `#[event]` attributes
114#[derive(Debug, Default)]
115pub struct Overrides {
116    /// Override the default 8-byte discriminator
117    // `Box` is used to avoid large memory use in the common case as `Expr` is a large type
118    pub discriminator: Option<Box<Expr>>,
119}
120
121impl Parse for Overrides {
122    fn parse(input: ParseStream) -> ParseResult<Self> {
123        let mut attr = Self::default();
124        let args = input.parse_terminated(NamedArg::parse, Token![,])?;
125        for arg in args {
126            match arg.name.to_string().as_str() {
127                "discriminator" => {
128                    let value = match arg.value {
129                        // Allow `discriminator = 42`
130                        Expr::Lit(ExprLit {
131                            lit: lit @ Lit::Int(_),
132                            ..
133                        }) => {
134                            parse_quote!(&[#lit])
135                        }
136                        // Allow `discriminator = [0, 1, 2, 3]`
137                        Expr::Array(arr) => {
138                            parse_quote!(&#arr)
139                        }
140                        expr => expr,
141                    };
142                    attr.discriminator.replace(Box::new(value))
143                }
144                name => {
145                    return Err(ParseError::new(
146                        arg.name.span(),
147                        format!(
148                            "Invalid argument `{}`. Expected one of: `discriminator`",
149                            name
150                        ),
151                    ));
152                }
153            };
154        }
155
156        Ok(attr)
157    }
158}
159
160struct NamedArg {
161    name: Ident,
162    #[allow(dead_code)]
163    eq_token: Token![=],
164    value: Expr,
165}
166
167impl Parse for NamedArg {
168    fn parse(input: ParseStream) -> ParseResult<Self> {
169        Ok(Self {
170            name: input.parse()?,
171            eq_token: input.parse()?,
172            value: input.parse()?,
173        })
174    }
175}
176
177#[derive(Debug)]
178pub struct IxArg {
179    pub name: Ident,
180    pub docs: Option<Vec<String>>,
181    pub raw_arg: PatType,
182}
183
184#[derive(Debug)]
185pub struct IxReturn {
186    pub ty: Type,
187}
188
189#[derive(Debug)]
190pub struct FallbackFn {
191    raw_method: ItemFn,
192}
193
194#[derive(Debug)]
195pub struct AccountsStruct {
196    // Name of the accounts struct.
197    pub ident: Ident,
198    // Generics + lifetimes on the accounts struct.
199    pub generics: Generics,
200    // Fields on the accounts struct.
201    pub fields: Vec<AccountField>,
202    // Instruction data api expression.
203    instruction_api: Option<Punctuated<syn::FnArg, Comma>>,
204}
205
206impl Parse for AccountsStruct {
207    fn parse(input: ParseStream) -> ParseResult<Self> {
208        let strct = <ItemStruct as Parse>::parse(input)?;
209        accounts_parser::parse(&strct)
210    }
211}
212
213impl From<&AccountsStruct> for TokenStream {
214    fn from(accounts: &AccountsStruct) -> Self {
215        accounts_codegen::generate(accounts)
216    }
217}
218
219impl ToTokens for AccountsStruct {
220    fn to_tokens(&self, tokens: &mut TokenStream) {
221        tokens.extend::<TokenStream>(self.into());
222    }
223}
224
225impl AccountsStruct {
226    pub fn new(
227        strct: ItemStruct,
228        fields: Vec<AccountField>,
229        instruction_api: Option<Punctuated<syn::FnArg, Comma>>,
230    ) -> Self {
231        let ident = strct.ident.clone();
232        let generics = strct.generics;
233        Self {
234            ident,
235            generics,
236            fields,
237            instruction_api,
238        }
239    }
240
241    // Return value maps instruction name to type.
242    // E.g. if we have `#[instruction(data: u64)]` then returns
243    // { "data": "u64"}.
244    pub fn instruction_args(&self) -> Option<HashMap<String, String>> {
245        self.instruction_api.as_ref().map(|instruction_api| {
246            instruction_api
247                .iter()
248                .map(|expr| {
249                    let arg = parser::tts_to_string(expr);
250                    let components: Vec<&str> = arg.split(" : ").collect();
251                    assert!(components.len() == 2);
252                    #[allow(
253                        clippy::indexing_slicing,
254                        reason = "len == 2 asserted immediately above"
255                    )]
256                    let result = (components[0].to_string(), components[1].to_string());
257                    result
258                })
259                .collect()
260        })
261    }
262
263    pub fn field_names(&self) -> Vec<String> {
264        self.fields
265            .iter()
266            .map(|field| field.ident().to_string())
267            .collect()
268    }
269
270    pub fn has_optional(&self) -> bool {
271        for field in &self.fields {
272            if let AccountField::Field(field) = field {
273                if field.is_optional {
274                    return true;
275                }
276            }
277        }
278        false
279    }
280
281    pub fn is_field_optional<T: quote::ToTokens>(&self, field: &T) -> bool {
282        let matching_field = self
283            .fields
284            .iter()
285            .find(|f| *f.ident() == parser::tts_to_string(field));
286        if let Some(matching_field) = matching_field {
287            matching_field.is_optional()
288        } else {
289            false
290        }
291    }
292}
293
294#[allow(clippy::large_enum_variant)]
295#[derive(Debug)]
296pub enum AccountField {
297    Field(Field),
298    CompositeField(CompositeField),
299}
300
301impl AccountField {
302    fn ident(&self) -> &Ident {
303        match self {
304            AccountField::Field(field) => &field.ident,
305            AccountField::CompositeField(c_field) => &c_field.ident,
306        }
307    }
308
309    fn is_optional(&self) -> bool {
310        match self {
311            AccountField::Field(field) => field.is_optional,
312            AccountField::CompositeField(_) => false,
313        }
314    }
315
316    pub fn ty_name(&self) -> Option<String> {
317        let qualified_ty_name = match self {
318            AccountField::Field(field) => match &field.ty {
319                Ty::Account(account) => Some(parser::tts_to_string(&account.account_type_path)),
320                Ty::LazyAccount(account) => Some(parser::tts_to_string(&account.account_type_path)),
321                _ => None,
322            },
323            AccountField::CompositeField(field) => Some(field.symbol.clone()),
324        };
325
326        qualified_ty_name.map(|name| match name.rsplit_once(" :: ") {
327            Some((_prefix, suffix)) => suffix.to_string(),
328            None => name,
329        })
330    }
331}
332
333#[derive(Debug)]
334pub struct Field {
335    pub ident: Ident,
336    pub constraints: ConstraintGroup,
337    pub ty: Ty,
338    pub is_optional: bool,
339    pub ty_span: Span,
340    /// IDL Doc comment
341    pub docs: Option<Vec<String>>,
342}
343
344impl Field {
345    pub fn typed_ident(&self) -> proc_macro2::TokenStream {
346        let name = &self.ident;
347        let ty_decl = self.ty_decl(false);
348        quote! {
349            #name: #ty_decl
350        }
351    }
352
353    pub fn ty_decl(&self, ignore_option: bool) -> proc_macro2::TokenStream {
354        let account_ty = self.account_ty();
355        let container_ty = self.container_ty();
356        let inner_ty = match &self.ty {
357            Ty::AccountInfo => quote! {
358                AccountInfo
359            },
360            Ty::UncheckedAccount => quote! {
361                UncheckedAccount
362            },
363            Ty::Signer => quote! {
364                Signer
365            },
366            Ty::ProgramData => quote! {
367                ProgramData
368            },
369            Ty::SystemAccount => quote! {
370                SystemAccount
371            },
372            Ty::Account(AccountTy { boxed, .. })
373            | Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => {
374                if *boxed {
375                    quote! {
376                        Box<#container_ty<#account_ty>>
377                    }
378                } else {
379                    quote! {
380                        #container_ty<#account_ty>
381                    }
382                }
383            }
384            Ty::Sysvar(ty) => {
385                let account = match ty {
386                    SysvarTy::Clock => quote! {Clock},
387                    SysvarTy::Rent => quote! {Rent},
388                    SysvarTy::EpochSchedule => quote! {EpochSchedule},
389                    SysvarTy::Fees => quote! {Fees},
390                    SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes},
391                    SysvarTy::SlotHashes => quote! {SlotHashes},
392                    SysvarTy::SlotHistory => quote! {SlotHistory},
393                    SysvarTy::StakeHistory => quote! {StakeHistory},
394                    SysvarTy::Instructions => quote! {Instructions},
395                    SysvarTy::Rewards => quote! {Rewards},
396                };
397                quote! {
398                    Sysvar<#account>
399                }
400            }
401            Ty::Program(ty) => {
402                let program = &ty.account_type_path;
403                // Check if this is the generic Program<'info> (unit type)
404                let program_str = quote!(#program).to_string();
405                if program_str == "__SolanaProgramUnitType" {
406                    quote! {
407                        #container_ty<'info>
408                    }
409                } else {
410                    quote! {
411                        #container_ty<'info, #program>
412                    }
413                }
414            }
415            Ty::Migration(ty) => {
416                let from = &ty.from_type_path;
417                let to = &ty.to_type_path;
418                quote! {
419                    #container_ty<'info, #from, #to>
420                }
421            }
422            _ => quote! {
423                #container_ty<#account_ty>
424            },
425        };
426        if self.is_optional && !ignore_option {
427            quote! {
428                Option<#inner_ty>
429            }
430        } else {
431            quote! {
432                #inner_ty
433            }
434        }
435    }
436
437    // Ignores optional accounts. Optional account checks and handing should be done prior to this
438    // function being called.
439    pub fn from_account_info(
440        &self,
441        kind: Option<&InitKind>,
442        checked: bool,
443    ) -> proc_macro2::TokenStream {
444        let field = &self.ident;
445        let field_str = field.to_string();
446        let container_ty = self.container_ty();
447        let owner_addr = match &kind {
448            None => quote! { __program_id },
449            Some(InitKind::Program { .. }) => quote! {
450                __program_id
451            },
452            _ => quote! {
453                &anchor_spl::token::ID
454            },
455        };
456        match &self.ty {
457            Ty::AccountInfo => quote! { #field.to_account_info() },
458            Ty::UncheckedAccount => {
459                quote! { UncheckedAccount::try_from(&#field) }
460            }
461            Ty::Account(AccountTy { boxed, .. })
462            | Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => {
463                let stream = if checked {
464                    quote! {
465                        match #container_ty::try_from(&#field) {
466                            Ok(val) => val,
467                            Err(e) => return Err(e.with_account_name(#field_str))
468                        }
469                    }
470                } else {
471                    quote! {
472                        match #container_ty::try_from_unchecked(&#field) {
473                            Ok(val) => val,
474                            Err(e) => return Err(e.with_account_name(#field_str))
475                        }
476                    }
477                };
478                if *boxed {
479                    quote! {
480                        Box::new(#stream)
481                    }
482                } else {
483                    stream
484                }
485            }
486            Ty::LazyAccount(_) => {
487                if checked {
488                    quote! {
489                        match #container_ty::try_from(&#field) {
490                            Ok(val) => val,
491                            Err(e) => return Err(e.with_account_name(#field_str))
492                        }
493                    }
494                } else {
495                    quote! {
496                        match #container_ty::try_from_unchecked(&#field) {
497                            Ok(val) => val,
498                            Err(e) => return Err(e.with_account_name(#field_str))
499                        }
500                    }
501                }
502            }
503            Ty::AccountLoader(_) => {
504                if checked {
505                    quote! {
506                        match #container_ty::try_from(&#field) {
507                            Ok(val) => val,
508                            Err(e) => return Err(e.with_account_name(#field_str))
509                        }
510                    }
511                } else {
512                    quote! {
513                        match #container_ty::try_from_unchecked(#owner_addr, &#field) {
514                            Ok(val) => val,
515                            Err(e) => return Err(e.with_account_name(#field_str))
516                        }
517                    }
518                }
519            }
520            _ => {
521                if checked {
522                    quote! {
523                        match #container_ty::try_from(#owner_addr, &#field) {
524                            Ok(val) => val,
525                            Err(e) => return Err(e.with_account_name(#field_str))
526                        }
527                    }
528                } else {
529                    quote! {
530                        match #container_ty::try_from_unchecked(#owner_addr, &#field) {
531                            Ok(val) => val,
532                            Err(e) => return Err(e.with_account_name(#field_str))
533                        }
534                    }
535                }
536            }
537        }
538    }
539
540    pub fn container_ty(&self) -> proc_macro2::TokenStream {
541        match &self.ty {
542            Ty::Account(_) => quote! {
543                anchor_lang::accounts::account::Account
544            },
545            Ty::LazyAccount(_) => quote! {
546                anchor_lang::accounts::lazy_account::LazyAccount
547            },
548            Ty::AccountLoader(_) => quote! {
549                anchor_lang::accounts::account_loader::AccountLoader
550            },
551            Ty::Migration(_) => quote! {
552                anchor_lang::accounts::migration::Migration
553            },
554            Ty::Sysvar(_) => quote! { anchor_lang::accounts::sysvar::Sysvar },
555            Ty::Program(_) => quote! { anchor_lang::accounts::program::Program },
556            Ty::Interface(_) => quote! { anchor_lang::accounts::interface::Interface },
557            Ty::InterfaceAccount(_) => {
558                quote! { anchor_lang::accounts::interface_account::InterfaceAccount }
559            }
560            Ty::AccountInfo => quote! {},
561            Ty::UncheckedAccount => quote! {},
562            Ty::Signer => quote! {},
563            Ty::SystemAccount => quote! {},
564            Ty::ProgramData => quote! {},
565        }
566    }
567
568    // Returns the inner account struct type.
569    pub fn account_ty(&self) -> proc_macro2::TokenStream {
570        match &self.ty {
571            Ty::AccountInfo => quote! {
572                AccountInfo
573            },
574            Ty::UncheckedAccount => quote! {
575                UncheckedAccount
576            },
577            Ty::Signer => quote! {
578                Signer
579            },
580            Ty::SystemAccount => quote! {
581                SystemAccount
582            },
583            Ty::ProgramData => quote! {
584                ProgramData
585            },
586            Ty::Account(ty) => {
587                let ident = &ty.account_type_path;
588                quote! {
589                    #ident
590                }
591            }
592            Ty::LazyAccount(ty) => {
593                let ident = &ty.account_type_path;
594                quote! {
595                    #ident
596                }
597            }
598            Ty::InterfaceAccount(ty) => {
599                let ident = &ty.account_type_path;
600                quote! {
601                    #ident
602                }
603            }
604            Ty::AccountLoader(ty) => {
605                let ident = &ty.account_type_path;
606                quote! {
607                    #ident
608                }
609            }
610            Ty::Migration(ty) => {
611                // Return just the From type for IDL and other uses
612                let from = &ty.from_type_path;
613                quote! {
614                    #from
615                }
616            }
617            Ty::Sysvar(ty) => match ty {
618                SysvarTy::Clock => quote! {Clock},
619                SysvarTy::Rent => quote! {Rent},
620                SysvarTy::EpochSchedule => quote! {EpochSchedule},
621                SysvarTy::Fees => quote! {Fees},
622                SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes},
623                SysvarTy::SlotHashes => quote! {SlotHashes},
624                SysvarTy::SlotHistory => quote! {SlotHistory},
625                SysvarTy::StakeHistory => quote! {StakeHistory},
626                SysvarTy::Instructions => quote! {Instructions},
627                SysvarTy::Rewards => quote! {Rewards},
628            },
629            Ty::Program(ty) => {
630                let program = &ty.account_type_path;
631                // Check if this is the special marker for generic Program<'info> (unit type)
632                let program_str = quote!(#program).to_string();
633                if program_str == "__SolanaProgramUnitType" {
634                    quote! {}
635                } else {
636                    quote! {
637                        #program
638                    }
639                }
640            }
641            Ty::Interface(ty) => {
642                let program = &ty.account_type_path;
643                quote! {
644                    #program
645                }
646            }
647        }
648    }
649}
650
651#[derive(Debug)]
652pub struct CompositeField {
653    pub ident: Ident,
654    pub constraints: ConstraintGroup,
655    pub symbol: String,
656    pub raw_field: syn::Field,
657    /// IDL Doc comment
658    pub docs: Option<Vec<String>>,
659}
660
661// A type of an account field.
662#[derive(Debug, PartialEq, Eq)]
663pub enum Ty {
664    AccountInfo,
665    UncheckedAccount,
666    AccountLoader(AccountLoaderTy),
667    Sysvar(SysvarTy),
668    Account(AccountTy),
669    LazyAccount(LazyAccountTy),
670    Migration(MigrationTy),
671    Program(ProgramTy),
672    Interface(InterfaceTy),
673    InterfaceAccount(InterfaceAccountTy),
674    Signer,
675    SystemAccount,
676    ProgramData,
677}
678
679#[derive(Debug, PartialEq, Eq)]
680pub enum SysvarTy {
681    Clock,
682    Rent,
683    EpochSchedule,
684    Fees,
685    RecentBlockhashes,
686    SlotHashes,
687    SlotHistory,
688    StakeHistory,
689    Instructions,
690    Rewards,
691}
692
693#[derive(Debug, PartialEq, Eq)]
694pub struct AccountLoaderTy {
695    // The struct type of the account.
696    pub account_type_path: TypePath,
697}
698
699#[derive(Debug, PartialEq, Eq)]
700pub struct AccountTy {
701    // The struct type of the account.
702    pub account_type_path: TypePath,
703    // True if the account has been boxed via `Box<T>`.
704    pub boxed: bool,
705}
706
707#[derive(Debug, PartialEq, Eq)]
708pub struct LazyAccountTy {
709    // The struct type of the account.
710    pub account_type_path: TypePath,
711}
712
713#[derive(Debug, PartialEq, Eq)]
714pub struct MigrationTy {
715    // Migration<'info, From, To> - we need both From and To types
716    pub from_type_path: TypePath,
717    pub to_type_path: TypePath,
718}
719
720#[derive(Debug, PartialEq, Eq)]
721pub struct InterfaceAccountTy {
722    // The struct type of the account.
723    pub account_type_path: TypePath,
724    // True if the account has been boxed via `Box<T>`.
725    pub boxed: bool,
726}
727
728#[derive(Debug, PartialEq, Eq)]
729pub struct ProgramTy {
730    // The struct type of the account.
731    pub account_type_path: TypePath,
732}
733
734#[derive(Debug, PartialEq, Eq)]
735pub struct InterfaceTy {
736    // The struct type of the account.
737    pub account_type_path: TypePath,
738}
739
740#[derive(Debug)]
741pub struct Error {
742    pub name: String,
743    pub raw_enum: ItemEnum,
744    pub ident: Ident,
745    pub codes: Vec<ErrorCode>,
746    pub args: Option<ErrorArgs>,
747}
748
749#[derive(Debug)]
750pub struct ErrorArgs {
751    pub offset: LitInt,
752}
753
754impl Parse for ErrorArgs {
755    fn parse(stream: ParseStream) -> ParseResult<Self> {
756        let offset_span = stream.span();
757        let offset = stream.call(Ident::parse_any)?;
758        if offset.to_string().as_str() != "offset" {
759            return Err(ParseError::new(offset_span, "expected keyword offset"));
760        }
761        stream.parse::<Token![=]>()?;
762        let offset: LitInt = stream.parse()?;
763        Ok(ErrorArgs { offset })
764    }
765}
766
767#[derive(Debug)]
768pub struct ErrorCode {
769    pub id: u32,
770    pub ident: Ident,
771    pub msg: Option<String>,
772}
773
774// All well formed constraints on a single `Accounts` field.
775#[derive(Debug, Default, Clone)]
776pub struct ConstraintGroup {
777    pub init: Option<ConstraintInitGroup>,
778    pub zeroed: Option<ConstraintZeroed>,
779    pub mutable: Option<ConstraintMut>,
780    pub dup: Option<ConstraintDup>,
781    pub signer: Option<ConstraintSigner>,
782    pub owner: Option<ConstraintOwner>,
783    pub rent_exempt: Option<ConstraintRentExempt>,
784    pub seeds: Option<ConstraintSeedsGroup>,
785    pub executable: Option<ConstraintExecutable>,
786    pub has_one: Vec<ConstraintHasOne>,
787    pub raw: Vec<ConstraintRaw>,
788    pub close: Option<ConstraintClose>,
789    pub address: Option<ConstraintAddress>,
790    pub associated_token: Option<ConstraintAssociatedToken>,
791    pub token_account: Option<ConstraintTokenAccountGroup>,
792    pub mint: Option<ConstraintTokenMintGroup>,
793    pub realloc: Option<ConstraintReallocGroup>,
794}
795
796impl ConstraintGroup {
797    pub fn is_zeroed(&self) -> bool {
798        self.zeroed.is_some()
799    }
800
801    pub fn is_mutable(&self) -> bool {
802        self.mutable.is_some()
803    }
804
805    pub fn is_dup(&self) -> bool {
806        self.dup.is_some()
807    }
808
809    /// Returns `true` when the field has `#[account(init, ...)]` but **not**
810    /// `#[account(init_if_needed, ...)]`.
811    pub fn is_pure_init(&self) -> bool {
812        matches!(&self.init, Some(c) if !c.if_needed)
813    }
814
815    pub fn is_signer(&self) -> bool {
816        self.signer.is_some()
817    }
818
819    pub fn is_close(&self) -> bool {
820        self.close.is_some()
821    }
822}
823
824// A single account constraint *after* merging all tokens into a well formed
825// constraint. Some constraints like "seeds" are defined by multiple
826// tokens, so a merging phase is required.
827#[allow(clippy::large_enum_variant)]
828#[derive(Debug)]
829pub enum Constraint {
830    Init(ConstraintInitGroup),
831    Zeroed(ConstraintZeroed),
832    Mut(ConstraintMut),
833    Dup(ConstraintDup),
834    Signer(ConstraintSigner),
835    HasOne(ConstraintHasOne),
836    Raw(ConstraintRaw),
837    Owner(ConstraintOwner),
838    RentExempt(ConstraintRentExempt),
839    Seeds(ConstraintSeedsGroup),
840    AssociatedToken(ConstraintAssociatedToken),
841    Executable(ConstraintExecutable),
842    Close(ConstraintClose),
843    Address(ConstraintAddress),
844    TokenAccount(ConstraintTokenAccountGroup),
845    Mint(ConstraintTokenMintGroup),
846    Realloc(ConstraintReallocGroup),
847}
848
849// Constraint token is a single keyword in a `#[account(<TOKEN>)]` attribute.
850#[allow(clippy::large_enum_variant)]
851#[derive(Debug)]
852pub enum ConstraintToken {
853    Init(Context<ConstraintInit>),
854    Zeroed(Context<ConstraintZeroed>),
855    Mut(Context<ConstraintMut>),
856    Dup(Context<ConstraintDup>),
857    Signer(Context<ConstraintSigner>),
858    HasOne(Context<ConstraintHasOne>),
859    Raw(Context<ConstraintRaw>),
860    Owner(Context<ConstraintOwner>),
861    RentExempt(Context<ConstraintRentExempt>),
862    Seeds(Context<ConstraintSeeds>),
863    Executable(Context<ConstraintExecutable>),
864    Close(Context<ConstraintClose>),
865    Payer(Context<ConstraintPayer>),
866    Space(Context<ConstraintSpace>),
867    Address(Context<ConstraintAddress>),
868    TokenMint(Context<ConstraintTokenMint>),
869    TokenAuthority(Context<ConstraintTokenAuthority>),
870    TokenTokenProgram(Context<ConstraintTokenProgram>),
871    AssociatedTokenMint(Context<ConstraintTokenMint>),
872    AssociatedTokenAuthority(Context<ConstraintTokenAuthority>),
873    AssociatedTokenTokenProgram(Context<ConstraintTokenProgram>),
874    MintAuthority(Context<ConstraintMintAuthority>),
875    MintFreezeAuthority(Context<ConstraintMintFreezeAuthority>),
876    MintDecimals(Context<ConstraintMintDecimals>),
877    MintTokenProgram(Context<ConstraintTokenProgram>),
878    Bump(Context<ConstraintTokenBump>),
879    ProgramSeed(Context<ConstraintProgramSeed>),
880    Realloc(Context<ConstraintRealloc>),
881    ReallocPayer(Context<ConstraintReallocPayer>),
882    ReallocZero(Context<ConstraintReallocZero>),
883    // extensions
884    ExtensionGroupPointerAuthority(Context<ConstraintExtensionAuthority>),
885    ExtensionGroupPointerGroupAddress(Context<ConstraintExtensionGroupPointerGroupAddress>),
886    ExtensionGroupMemberPointerAuthority(Context<ConstraintExtensionAuthority>),
887    ExtensionGroupMemberPointerMemberAddress(
888        Context<ConstraintExtensionGroupMemberPointerMemberAddress>,
889    ),
890    ExtensionMetadataPointerAuthority(Context<ConstraintExtensionAuthority>),
891    ExtensionMetadataPointerMetadataAddress(
892        Context<ConstraintExtensionMetadataPointerMetadataAddress>,
893    ),
894    ExtensionCloseAuthority(Context<ConstraintExtensionAuthority>),
895    ExtensionTokenHookAuthority(Context<ConstraintExtensionAuthority>),
896    ExtensionTokenHookProgramId(Context<ConstraintExtensionTokenHookProgramId>),
897    ExtensionPermanentDelegate(Context<ConstraintExtensionPermanentDelegate>),
898    ExtensionPausableAuthority(Context<ConstraintExtensionAuthority>),
899}
900
901impl Parse for ConstraintToken {
902    fn parse(stream: ParseStream) -> ParseResult<Self> {
903        accounts_parser::constraints::parse_token(stream)
904    }
905}
906
907#[derive(Debug, Clone)]
908pub struct ConstraintInit {
909    pub if_needed: bool,
910}
911
912#[derive(Debug, Clone)]
913pub struct ConstraintInitIfNeeded {}
914
915#[derive(Debug, Clone)]
916pub struct ConstraintZeroed {}
917
918#[derive(Debug, Clone)]
919pub struct ConstraintMut {
920    pub error: Option<Expr>,
921}
922
923#[derive(Debug, Clone)]
924pub struct ConstraintDup {}
925
926#[derive(Debug, Clone)]
927pub struct ConstraintReallocGroup {
928    pub payer: Expr,
929    pub space: Expr,
930    pub zero: Expr,
931}
932
933#[derive(Debug, Clone)]
934pub struct ConstraintRealloc {
935    pub space: Expr,
936}
937
938#[derive(Debug, Clone)]
939pub struct ConstraintReallocPayer {
940    pub target: Expr,
941}
942
943#[derive(Debug, Clone)]
944pub struct ConstraintReallocZero {
945    pub zero: Expr,
946}
947
948#[derive(Debug, Clone)]
949pub struct ConstraintSigner {
950    pub error: Option<Expr>,
951}
952
953#[derive(Debug, Clone)]
954pub struct ConstraintHasOne {
955    pub join_target: Expr,
956    pub error: Option<Expr>,
957}
958
959#[derive(Debug, Clone)]
960pub struct ConstraintRaw {
961    pub raw: Expr,
962    pub error: Option<Expr>,
963}
964
965#[derive(Debug, Clone)]
966pub struct ConstraintOwner {
967    pub owner_address: Expr,
968    pub error: Option<Expr>,
969}
970
971#[derive(Debug, Clone)]
972pub struct ConstraintAddress {
973    pub address: Expr,
974    pub error: Option<Expr>,
975}
976
977#[derive(Debug, Clone)]
978pub enum ConstraintRentExempt {
979    Enforce,
980    Skip,
981}
982
983#[derive(Debug, Clone)]
984pub struct ConstraintInitGroup {
985    pub if_needed: bool,
986    pub seeds: Option<ConstraintSeedsGroup>,
987    pub payer: Expr,
988    pub space: Option<Expr>,
989    pub kind: InitKind,
990}
991
992/// Seeds can be written as a literal slice (`[ a, b ]`) or any
993/// expression that produces `&[&[u8]]` at run time.
994#[derive(Debug, Clone)]
995pub enum SeedsExpr {
996    /// Example: `[ b"prefix".as_ref(), key.as_ref() ]`
997    List(Punctuated<Expr, Token![,]>),
998    /// Example: `pda_seeds(key)`
999    Expr(Box<Expr>),
1000}
1001
1002impl SeedsExpr {
1003    /// Return the underlying `Punctuated` if this is the `List` form.
1004    fn list_mut(&mut self) -> Option<&mut Punctuated<Expr, Token![,]>> {
1005        match self {
1006            SeedsExpr::List(list) => Some(list),
1007            SeedsExpr::Expr(_) => None,
1008        }
1009    }
1010
1011    /// Mirrors `Punctuated::pop`: removes and returns the last element and its
1012    /// trailing punctuation when this is the `List` variant. For the `Expr variant`,
1013    /// which represents a single non-list seed expression, returns `None` because
1014    /// there is no list to pop from.
1015    pub fn pop(&mut self) -> Option<syn::punctuated::Pair<Expr, Token![,]>> {
1016        self.list_mut()?.pop()
1017    }
1018
1019    pub fn push_value(&mut self, value: Expr) {
1020        if let Some(list) = self.list_mut() {
1021            list.push_value(value);
1022        }
1023    }
1024
1025    /// Mirrors `Punctuated::push_value`: pushes a value without punctuation onto
1026    /// the underlying list when this is the `List` variant. No-op for the `Expr`
1027    /// variant.
1028    pub fn is_empty(&self) -> bool {
1029        match self {
1030            SeedsExpr::List(list) => list.is_empty(),
1031            SeedsExpr::Expr(_) => false, // Treat as “one seed”
1032        }
1033    }
1034
1035    /// Immutable iteration over every seed expression, regardless of variant
1036    pub fn iter(&self) -> Box<dyn Iterator<Item = &Expr> + '_> {
1037        match self {
1038            SeedsExpr::List(list) => Box::new(list.iter()),
1039            SeedsExpr::Expr(expr) => Box::new(std::iter::once(expr.as_ref())),
1040        }
1041    }
1042
1043    /// The number of seeds represented: `list.len()` for `List` and `1` for `Expr`.
1044    pub fn len(&self) -> usize {
1045        match self {
1046            SeedsExpr::List(list) => list.len(),
1047            SeedsExpr::Expr(_) => 1,
1048        }
1049    }
1050}
1051
1052/// Allow `quote!{ #seeds }`
1053impl quote::ToTokens for SeedsExpr {
1054    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
1055        match self {
1056            SeedsExpr::List(list) => list.to_tokens(tokens),
1057            SeedsExpr::Expr(expr) => expr.to_tokens(tokens),
1058        }
1059    }
1060}
1061
1062impl syn::parse::Parse for SeedsExpr {
1063    fn parse(stream: syn::parse::ParseStream) -> syn::parse::Result<Self> {
1064        if stream.peek(syn::token::Bracket) {
1065            let content;
1066            syn::bracketed!(content in stream);
1067            let mut list: Punctuated<Expr, Token![,]> =
1068                content.parse_terminated(Expr::parse, Token![,])?;
1069            list.pop_punct();
1070
1071            Ok(SeedsExpr::List(list))
1072        } else {
1073            Ok(SeedsExpr::Expr(Box::new(stream.parse()?)))
1074        }
1075    }
1076}
1077
1078#[derive(Debug, Clone)]
1079pub struct ConstraintSeedsGroup {
1080    pub is_init: bool,
1081    pub seeds: SeedsExpr,
1082    pub bump: Option<Expr>,         // None => bump was given without a target.
1083    pub program_seed: Option<Expr>, // None => use the current program's program_id.
1084}
1085
1086#[derive(Debug, Clone)]
1087pub struct ConstraintSeeds {
1088    pub seeds: SeedsExpr,
1089}
1090
1091#[derive(Debug, Clone)]
1092pub struct ConstraintExecutable {}
1093
1094#[derive(Debug, Clone)]
1095pub struct ConstraintPayer {
1096    pub target: Expr,
1097}
1098
1099#[derive(Debug, Clone)]
1100pub struct ConstraintSpace {
1101    pub space: Expr,
1102}
1103
1104// extension constraints
1105#[derive(Debug, Clone)]
1106pub struct ConstraintExtensionAuthority {
1107    pub authority: Expr,
1108}
1109
1110#[derive(Debug, Clone)]
1111pub struct ConstraintExtensionGroupPointerGroupAddress {
1112    pub group_address: Expr,
1113}
1114
1115#[derive(Debug, Clone)]
1116pub struct ConstraintExtensionGroupMemberPointerMemberAddress {
1117    pub member_address: Expr,
1118}
1119
1120#[derive(Debug, Clone)]
1121pub struct ConstraintExtensionMetadataPointerMetadataAddress {
1122    pub metadata_address: Expr,
1123}
1124
1125#[derive(Debug, Clone)]
1126pub struct ConstraintExtensionTokenHookProgramId {
1127    pub program_id: Expr,
1128}
1129
1130#[derive(Debug, Clone)]
1131pub struct ConstraintExtensionPermanentDelegate {
1132    pub permanent_delegate: Expr,
1133}
1134
1135#[derive(Debug, Clone)]
1136#[allow(clippy::large_enum_variant)]
1137pub enum InitKind {
1138    Program {
1139        owner: Option<Expr>,
1140    },
1141    Interface {
1142        owner: Option<Expr>,
1143    },
1144    // Owner for token and mint represents the authority. Not to be confused
1145    // with the owner of the AccountInfo.
1146    Token {
1147        owner: Expr,
1148        mint: Expr,
1149        token_program: Option<Expr>,
1150    },
1151    AssociatedToken {
1152        owner: Expr,
1153        mint: Expr,
1154        token_program: Option<Expr>,
1155    },
1156    Mint {
1157        owner: Expr,
1158        freeze_authority: Option<Expr>,
1159        decimals: Expr,
1160        token_program: Option<Expr>,
1161        // extensions
1162        group_pointer_authority: Option<Expr>,
1163        group_pointer_group_address: Option<Expr>,
1164        group_member_pointer_authority: Option<Expr>,
1165        group_member_pointer_member_address: Option<Expr>,
1166        metadata_pointer_authority: Option<Expr>,
1167        metadata_pointer_metadata_address: Option<Expr>,
1168        close_authority: Option<Expr>,
1169        permanent_delegate: Option<Expr>,
1170        transfer_hook_authority: Option<Expr>,
1171        transfer_hook_program_id: Option<Expr>,
1172        pausable_authority: Option<Expr>,
1173    },
1174}
1175
1176#[derive(Debug, Clone)]
1177pub struct ConstraintClose {
1178    pub sol_dest: Ident,
1179}
1180
1181#[derive(Debug, Clone)]
1182pub struct ConstraintTokenMint {
1183    pub mint: Expr,
1184}
1185
1186#[derive(Debug, Clone)]
1187pub struct ConstraintMintConfidentialTransferData {
1188    pub confidential_transfer_data: Expr,
1189}
1190
1191#[derive(Debug, Clone)]
1192pub struct ConstraintMintMetadata {
1193    pub token_metadata: Expr,
1194}
1195
1196#[derive(Debug, Clone)]
1197pub struct ConstraintMintTokenGroupData {
1198    pub token_group_data: Expr,
1199}
1200
1201#[derive(Debug, Clone)]
1202pub struct ConstraintMintTokenGroupMemberData {
1203    pub token_group_member_data: Expr,
1204}
1205
1206#[derive(Debug, Clone)]
1207pub struct ConstraintMintMetadataPointerData {
1208    pub metadata_pointer_data: Expr,
1209}
1210
1211#[derive(Debug, Clone)]
1212pub struct ConstraintMintGroupPointerData {
1213    pub group_pointer_data: Expr,
1214}
1215
1216#[derive(Debug, Clone)]
1217pub struct ConstraintMintGroupMemberPointerData {
1218    pub group_member_pointer_data: Expr,
1219}
1220
1221#[derive(Debug, Clone)]
1222pub struct ConstraintMintCloseAuthority {
1223    pub close_authority: Expr,
1224}
1225
1226#[derive(Debug, Clone)]
1227pub struct ConstraintTokenAuthority {
1228    pub auth: Expr,
1229}
1230
1231#[derive(Debug, Clone)]
1232pub struct ConstraintTokenProgram {
1233    token_program: Expr,
1234}
1235
1236#[derive(Debug, Clone)]
1237pub struct ConstraintMintAuthority {
1238    pub mint_auth: Expr,
1239}
1240
1241#[derive(Debug, Clone)]
1242pub struct ConstraintMintFreezeAuthority {
1243    pub mint_freeze_auth: Expr,
1244}
1245
1246#[derive(Debug, Clone)]
1247pub struct ConstraintMintDecimals {
1248    pub decimals: Expr,
1249}
1250
1251#[derive(Debug, Clone)]
1252pub struct ConstraintTokenBump {
1253    pub bump: Option<Expr>,
1254}
1255
1256#[derive(Debug, Clone)]
1257pub struct ConstraintProgramSeed {
1258    pub program_seed: Expr,
1259}
1260
1261#[derive(Debug, Clone)]
1262pub struct ConstraintAssociatedToken {
1263    pub wallet: Expr,
1264    pub mint: Expr,
1265    pub token_program: Option<Expr>,
1266}
1267
1268#[derive(Debug, Clone)]
1269pub struct ConstraintTokenAccountGroup {
1270    pub mint: Option<Expr>,
1271    pub authority: Option<Expr>,
1272    pub token_program: Option<Expr>,
1273}
1274
1275#[derive(Debug, Clone)]
1276pub struct ConstraintTokenMintGroup {
1277    pub decimals: Option<Expr>,
1278    pub mint_authority: Option<Expr>,
1279    pub freeze_authority: Option<Expr>,
1280    pub token_program: Option<Expr>,
1281    pub group_pointer_authority: Option<Expr>,
1282    pub group_pointer_group_address: Option<Expr>,
1283    pub group_member_pointer_authority: Option<Expr>,
1284    pub group_member_pointer_member_address: Option<Expr>,
1285    pub metadata_pointer_authority: Option<Expr>,
1286    pub metadata_pointer_metadata_address: Option<Expr>,
1287    pub close_authority: Option<Expr>,
1288    pub permanent_delegate: Option<Expr>,
1289    pub transfer_hook_authority: Option<Expr>,
1290    pub transfer_hook_program_id: Option<Expr>,
1291    pub pausable_authority: Option<Expr>,
1292}
1293
1294// Syntax context object for preserving metadata about the inner item.
1295#[derive(Debug, Clone)]
1296pub struct Context<T> {
1297    span: Span,
1298    inner: T,
1299}
1300
1301impl<T> Context<T> {
1302    pub fn new(span: Span, inner: T) -> Self {
1303        Self { span, inner }
1304    }
1305
1306    pub fn into_inner(self) -> T {
1307        self.inner
1308    }
1309}
1310
1311impl<T> Deref for Context<T> {
1312    type Target = T;
1313
1314    fn deref(&self) -> &Self::Target {
1315        &self.inner
1316    }
1317}
1318
1319impl<T> Context<T> {
1320    pub fn span(&self) -> Span {
1321        self.span
1322    }
1323}
1324
1325impl<T: ToTokens> ToTokens for Context<T> {
1326    fn to_tokens(&self, tokens: &mut TokenStream) {
1327        self.inner.to_tokens(tokens)
1328    }
1329}