anchor_syn/idl/
program.rs

1use anyhow::{anyhow, Result};
2use heck::CamelCase;
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use syn::spanned::Spanned;
6
7use super::{
8    common::{gen_print_section, get_idl_module_path, get_no_docs, get_program_path},
9    defined::gen_idl_type,
10};
11use crate::{
12    parser::{context::CrateContext, docs},
13    Program,
14};
15
16/// Generate the IDL build print function for the program module.
17pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream {
18    check_safety_comments().unwrap_or_else(|e| panic!("Safety checks failed: {e}"));
19
20    let idl = get_idl_module_path();
21    let no_docs = get_no_docs();
22
23    let name = program.name.to_string();
24    let docs = match &program.docs {
25        Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
26        _ => quote! { vec![] },
27    };
28
29    let result = program
30        .ixs
31        .iter()
32        .map(|ix| {
33            let name = ix.ident.to_string();
34            let name_pascal = format_ident!("{}", name.to_camel_case());
35            let ctx_ident = &ix.anchor_ident;
36            let cfgs = &ix.cfgs;
37
38            let docs = match &ix.docs {
39                Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
40                _ => quote! { vec![] },
41            };
42
43            let (args, mut defined) = ix
44                .args
45                .iter()
46                .map(|arg| {
47                    let name = arg.name.to_string();
48                    let docs = match docs::parse(&arg.raw_arg.attrs) {
49                        Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
50                        _ => quote! { vec![] },
51                    };
52                    let (ty, defined) = gen_idl_type(&arg.raw_arg.ty, &[])
53                        .map_err(|_| syn::Error::new(arg.raw_arg.ty.span(), "Unsupported type"))?;
54
55                    Ok((
56                        quote! {
57                            #idl::IdlField {
58                                name: #name.into(),
59                                docs: #docs,
60                                ty: #ty,
61                            }
62                        },
63                        defined,
64                    ))
65                })
66                .collect::<syn::Result<Vec<_>>>()?
67                .into_iter()
68                .unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
69
70            let returns = match gen_idl_type(&ix.returns.ty, &[]) {
71                Ok((ty, def)) => {
72                    defined.push(def);
73                    quote! { Some(#ty) }
74                }
75                _ => quote! { None },
76            };
77
78            Ok((
79                quote! {
80                    #(#cfgs)*
81                    #idl::IdlInstruction {
82                        name: #name.into(),
83                        docs: #docs,
84                        discriminator: crate::instruction::#name_pascal::DISCRIMINATOR.into(),
85                        accounts: #ctx_ident::__anchor_private_gen_idl_accounts(
86                            &mut accounts,
87                            &mut types,
88                        ),
89                        args: vec![#(#args),*],
90                        returns: #returns,
91                    }
92                },
93                defined,
94            ))
95        })
96        .collect::<syn::Result<Vec<_>>>();
97    let (instructions, defined) = match result {
98        Err(e) => return e.into_compile_error(),
99        Ok(v) => v.into_iter().unzip::<_, Vec<_>, Vec<_>, Vec<_>>(),
100    };
101    let defined = defined.into_iter().flatten().flatten().collect::<Vec<_>>();
102
103    let fn_body = gen_print_section(
104        "program",
105        quote! {
106            let mut accounts: std::collections::BTreeMap<String, #idl::IdlAccount> =
107                std::collections::BTreeMap::new();
108            let mut types: std::collections::BTreeMap<String, #idl::IdlTypeDef> =
109                std::collections::BTreeMap::new();
110
111            #(
112                if let Some(ty) = <#defined>::create_type() {
113                    types.insert(<#defined>::get_full_path(), ty);
114                    <#defined>::insert_types(&mut types);
115                }
116            );*
117
118            #idl::Idl {
119                address: Default::default(),
120                metadata: #idl::IdlMetadata {
121                    name: #name.into(),
122                    version: env!("CARGO_PKG_VERSION").into(),
123                    spec: #idl::IDL_SPEC.into(),
124                    description: option_env!("CARGO_PKG_DESCRIPTION")
125                        .filter(|d| !d.is_empty())
126                        .map(|d| d.into()),
127                    repository: option_env!("CARGO_PKG_REPOSITORY")
128                        .filter(|r| !r.is_empty())
129                        .map(|r| r.into()),
130                    dependencies: Default::default(),
131                    contact: Default::default(),
132                    deployments: Default::default(),
133                },
134                docs: #docs,
135                instructions: vec![#(#instructions),*],
136                accounts: accounts.into_values().collect(),
137                events: Default::default(),
138                errors: Default::default(),
139                types: types.into_values().collect(),
140                constants: Default::default(),
141            }
142        },
143    );
144
145    quote! {
146        #[test]
147        pub fn __anchor_private_print_idl_program() {
148            #fn_body
149        }
150    }
151}
152
153/// Check safety comments.
154fn check_safety_comments() -> Result<()> {
155    let skip_lint = option_env!("ANCHOR_IDL_BUILD_SKIP_LINT")
156        .map(|val| val == "TRUE")
157        .unwrap_or_default();
158    if skip_lint {
159        return Ok(());
160    }
161
162    let program_path = get_program_path();
163    if program_path.is_err() {
164        // Getting the program path can fail in the following scenarios:
165        //
166        // - Anchor CLI version is incompatible with the current version
167        // - The error is coming from Rust Analyzer when the user has `idl-build` feature enabled,
168        // likely due to enabling all features (https://github.com/coral-xyz/anchor/issues/3042)
169        //
170        // For the first case, we have a warning when the user is using different versions of the
171        // lang and CLI crate. For the second case, users would either have to disable the
172        // `idl-build` feature, or define the program path environment variable in Rust Analyzer
173        // settings.
174        //
175        // Given this feature is not a critical one, and it works by default with `anchor build`,
176        // we can fail silently in the case of an error rather than panicking.
177        return Ok(());
178    }
179
180    program_path
181        .map(|path| path.join("src").join("lib.rs"))
182        .map(CrateContext::parse)?
183        .map_err(|e| anyhow!("Failed to parse crate: {e}"))?
184        .safety_checks()
185}