anchor_syn/codegen/accounts/
__client_accounts.rs

1use crate::{AccountField, AccountsStruct, Ty};
2use heck::SnakeCase;
3use quote::quote;
4use std::str::FromStr;
5
6// Generates the private `__client_accounts` mod implementation, containing
7// a generated struct mapping 1-1 to the `Accounts` struct, except with
8// `Pubkey`s as the types. This is generated for Rust *clients*.
9pub fn generate(
10    accs: &AccountsStruct,
11    program_id: proc_macro2::TokenStream,
12) -> proc_macro2::TokenStream {
13    let name = &accs.ident;
14    let account_mod_name: proc_macro2::TokenStream = format!(
15        "__client_accounts_{}",
16        accs.ident.to_string().to_snake_case()
17    )
18    .parse()
19    .unwrap();
20
21    let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
22        .fields
23        .iter()
24        .map(|f: &AccountField| match f {
25            AccountField::CompositeField(s) => {
26                let name = &s.ident;
27                let docs = if let Some(ref docs) = s.docs {
28                    docs.iter()
29                        .map(|docs_line| {
30                            proc_macro2::TokenStream::from_str(&format!(
31                                "#[doc = r#\"{docs_line}\"#]"
32                            ))
33                            .unwrap()
34                        })
35                        .collect()
36                } else {
37                    quote!()
38                };
39                let symbol: proc_macro2::TokenStream = format!(
40                    "__client_accounts_{0}::{1}",
41                    s.symbol.to_snake_case(),
42                    s.symbol,
43                )
44                .parse()
45                .unwrap();
46                quote! {
47                    #docs
48                    pub #name: #symbol
49                }
50            }
51            AccountField::Field(f) => {
52                let name = &f.ident;
53                let docs = if let Some(ref docs) = f.docs {
54                    docs.iter()
55                        .map(|docs_line| {
56                            proc_macro2::TokenStream::from_str(&format!(
57                                "#[doc = r#\"{docs_line}\"#]"
58                            ))
59                            .unwrap()
60                        })
61                        .collect()
62                } else {
63                    quote!()
64                };
65                if f.is_optional {
66                    quote! {
67                        #docs
68                        pub #name: Option<Pubkey>
69                    }
70                } else {
71                    quote! {
72                        #docs
73                        pub #name: Pubkey
74                    }
75                }
76            }
77        })
78        .collect();
79
80    let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
81        .fields
82        .iter()
83        .map(|f: &AccountField| match f {
84            AccountField::CompositeField(s) => {
85                let name = &s.ident;
86                quote! {
87                    account_metas.extend(self.#name.to_account_metas(None));
88                }
89            }
90            AccountField::Field(f) => {
91                let is_signer = match f.ty {
92                    Ty::Signer => true,
93                    _ => f.constraints.is_signer(),
94                };
95                let is_signer = match is_signer {
96                    false => quote! {false},
97                    true => quote! {true},
98                };
99                let meta = match f.constraints.is_mutable() {
100                    false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
101                    true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
102                };
103                let name = &f.ident;
104                if f.is_optional {
105                    quote! {
106                        if let Some(#name) = &self.#name {
107                            account_metas.push(#meta(*#name, #is_signer));
108                        } else {
109                            account_metas.push(anchor_lang::solana_program::instruction::AccountMeta::new_readonly(#program_id, false));
110                        }
111                    }
112                } else {
113                    quote! {
114                        account_metas.push(#meta(self.#name, #is_signer));
115                    }
116                }
117            }
118        })
119        .collect();
120    // Re-export all composite account structs (i.e. other structs deriving
121    // accounts embedded into this struct. Required because, these embedded
122    // structs are *not* visible from the #[program] macro, which is responsible
123    // for generating the `accounts` mod, which aggregates all the generated
124    // accounts used for structs.
125    let re_exports: Vec<proc_macro2::TokenStream> = {
126        // First, dedup the exports.
127        let mut re_exports = std::collections::HashSet::new();
128        for f in accs.fields.iter().filter_map(|f: &AccountField| match f {
129            AccountField::CompositeField(s) => Some(s),
130            AccountField::Field(_) => None,
131        }) {
132            re_exports.insert(format!(
133                "__client_accounts_{0}::{1}",
134                f.symbol.to_snake_case(),
135                f.symbol,
136            ));
137        }
138
139        re_exports
140            .iter()
141            .map(|symbol: &String| {
142                let symbol: proc_macro2::TokenStream = symbol.parse().unwrap();
143                quote! {
144                    pub use #symbol;
145                }
146            })
147            .collect()
148    };
149
150    let struct_doc = proc_macro2::TokenStream::from_str(&format!(
151        "#[doc = \" Generated client accounts for [`{name}`].\"]"
152    ))
153    .unwrap();
154
155    quote! {
156        /// An internal, Anchor generated module. This is used (as an
157        /// implementation detail), to generate a struct for a given
158        /// `#[derive(Accounts)]` implementation, where each field is a Pubkey,
159        /// instead of an `AccountInfo`. This is useful for clients that want
160        /// to generate a list of accounts, without explicitly knowing the
161        /// order all the fields should be in.
162        ///
163        /// To access the struct in this module, one should use the sibling
164        /// `accounts` module (also generated), which re-exports this.
165        pub(crate) mod #account_mod_name {
166            use super::*;
167            use anchor_lang::prelude::borsh;
168            #(#re_exports)*
169
170            #struct_doc
171            #[derive(anchor_lang::AnchorSerialize)]
172            pub struct #name {
173                #(#account_struct_fields),*
174            }
175
176            #[automatically_derived]
177            impl anchor_lang::ToAccountMetas for #name {
178                fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
179                    let mut account_metas = vec![];
180
181                    #(#account_struct_metas)*
182
183                    account_metas
184                }
185            }
186        }
187    }
188}