From 85ec40d03cddc6b156feff57c17b8bc021bb33be Mon Sep 17 00:00:00 2001 From: acheron Date: Fri, 15 Sep 2023 22:53:11 +0200 Subject: [PATCH] Add `IdlBuild` trait --- lang/Cargo.toml | 2 + lang/attribute/account/src/lib.rs | 4 +- lang/attribute/event/src/lib.rs | 4 +- lang/derive/serde/src/lib.rs | 10 ++--- lang/src/lib.rs | 16 +++++--- lang/syn/src/codegen/accounts/mod.rs | 5 ++- lang/syn/src/codegen/error.rs | 4 +- lang/syn/src/codegen/program/mod.rs | 4 +- lang/syn/src/idl/build.rs | 56 +++++++++++++++++++++------- spl/Cargo.toml | 1 + spl/src/governance.rs | 3 ++ spl/src/metadata.rs | 9 +++++ spl/src/stake.rs | 3 ++ spl/src/token.rs | 6 +++ spl/src/token_interface.rs | 6 +++ tests/idl/programs/idl/Cargo.toml | 4 +- tests/idl/programs/idl/src/lib.rs | 5 +++ 17 files changed, 107 insertions(+), 35 deletions(-) diff --git a/lang/Cargo.toml b/lang/Cargo.toml index e2915f1f27..32be80a322 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -43,8 +43,10 @@ anchor-attribute-program = { path = "./attribute/program", version = "0.28.0" } anchor-derive-accounts = { path = "./derive/accounts", version = "0.28.0" } anchor-derive-serde = { path = "./derive/serde", version = "0.28.0" } anchor-derive-space = { path = "./derive/space", version = "0.28.0" } + # `anchor-syn` should only be included with `idl-build` feature anchor-syn = { path = "./syn", version = "0.28.0", optional = true } + arrayref = "0.3" base64 = "0.13" bincode = "1" diff --git a/lang/attribute/account/src/lib.rs b/lang/attribute/account/src/lib.rs index 8e9a732cb8..125b208d20 100644 --- a/lang/attribute/account/src/lib.rs +++ b/lang/attribute/account/src/lib.rs @@ -405,10 +405,10 @@ pub fn zero_copy( #[cfg(feature = "idl-build")] { let no_docs = get_no_docs(); - let idl_gen_impl = gen_idl_gen_impl_for_struct(&account_strct, no_docs); + let idl_build_impl = gen_idl_build_impl_for_struct(&account_strct, no_docs); return proc_macro::TokenStream::from(quote! { #ret - #idl_gen_impl + #idl_build_impl }); } diff --git a/lang/attribute/event/src/lib.rs b/lang/attribute/event/src/lib.rs index 2ab207bb36..994dc498d1 100644 --- a/lang/attribute/event/src/lib.rs +++ b/lang/attribute/event/src/lib.rs @@ -48,10 +48,10 @@ pub fn event( #[cfg(feature = "idl-build")] { - let idl_gen = anchor_syn::idl::build::gen_idl_print_function_for_event(&event_strct); + let idl_build = anchor_syn::idl::build::gen_idl_print_function_for_event(&event_strct); return proc_macro::TokenStream::from(quote! { #ret - #idl_gen + #idl_build }); } diff --git a/lang/derive/serde/src/lib.rs b/lang/derive/serde/src/lib.rs index 4fbbdb3517..248191fecd 100644 --- a/lang/derive/serde/src/lib.rs +++ b/lang/derive/serde/src/lib.rs @@ -37,12 +37,12 @@ pub fn anchor_serialize(input: TokenStream) -> TokenStream { { let no_docs = get_no_docs(); - let idl_gen_impl = match syn::parse(input).unwrap() { - Item::Struct(item) => gen_idl_gen_impl_for_struct(&item, no_docs), - Item::Enum(item) => gen_idl_gen_impl_for_enum(item, no_docs), + let idl_build_impl = match syn::parse(input).unwrap() { + Item::Struct(item) => gen_idl_build_impl_for_struct(&item, no_docs), + Item::Enum(item) => gen_idl_build_impl_for_enum(item, no_docs), Item::Union(item) => { // unions are not included in the IDL - TODO print a warning - idl_gen_impl_skeleton(quote! {None}, quote! {}, &item.ident, &item.generics) + idl_build_impl_skeleton(quote! {None}, quote! {}, &item.ident, &item.generics) } // Derive macros can only be defined on structs, enums, and unions. _ => unreachable!(), @@ -50,7 +50,7 @@ pub fn anchor_serialize(input: TokenStream) -> TokenStream { return TokenStream::from(quote! { #ret - #idl_gen_impl + #idl_build_impl }); }; diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 1ef35046de..99bc2a0fe8 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -50,19 +50,21 @@ pub use anchor_attribute_account::{account, declare_id, zero_copy}; pub use anchor_attribute_constant::constant; pub use anchor_attribute_error::*; pub use anchor_attribute_event::{emit, event}; -#[cfg(feature = "event-cpi")] -pub use anchor_attribute_event::{emit_cpi, event_cpi}; pub use anchor_attribute_program::program; pub use anchor_derive_accounts::Accounts; pub use anchor_derive_serde::{AnchorDeserialize, AnchorSerialize}; pub use anchor_derive_space::InitSpace; + /// Borsh is the default serialization format for instructions and accounts. pub use borsh::de::BorshDeserialize as AnchorDeserialize; pub use borsh::ser::BorshSerialize as AnchorSerialize; pub use solana_program; +#[cfg(feature = "event-cpi")] +pub use anchor_attribute_event::{emit_cpi, event_cpi}; + #[cfg(feature = "idl-build")] -pub use anchor_syn; +pub use anchor_syn::{self, idl::build::IdlBuild}; pub type Result = std::result::Result; @@ -353,8 +355,6 @@ pub mod prelude { AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, InitSpace, Key, Lamports, Owner, ProgramData, Result, Space, ToAccountInfo, ToAccountInfos, ToAccountMetas, }; - #[cfg(feature = "event-cpi")] - pub use super::{emit_cpi, event_cpi}; pub use anchor_attribute_error::*; pub use borsh; pub use error::*; @@ -373,6 +373,12 @@ pub mod prelude { pub use solana_program::sysvar::stake_history::StakeHistory; pub use solana_program::sysvar::Sysvar as SolanaSysvar; pub use thiserror; + + #[cfg(feature = "event-cpi")] + pub use super::{emit_cpi, event_cpi}; + + #[cfg(feature = "idl-build")] + pub use super::IdlBuild; } /// Internal module used by macros and unstable apis. diff --git a/lang/syn/src/codegen/accounts/mod.rs b/lang/syn/src/codegen/accounts/mod.rs index cc684b3f2e..5e10febcfe 100644 --- a/lang/syn/src/codegen/accounts/mod.rs +++ b/lang/syn/src/codegen/accounts/mod.rs @@ -36,10 +36,11 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { { #![allow(warnings)] let no_docs = crate::idl::build::get_no_docs(); - let idl_gen_impl = crate::idl::build::gen_idl_gen_impl_for_accounts_strct(&accs, no_docs); + let idl_build_impl = + crate::idl::build::gen_idl_build_impl_for_accounts_struct(&accs, no_docs); return quote! { #ret - #idl_gen_impl + #idl_build_impl }; } diff --git a/lang/syn/src/codegen/error.rs b/lang/syn/src/codegen/error.rs index 62c5e511be..2eedd7b489 100644 --- a/lang/syn/src/codegen/error.rs +++ b/lang/syn/src/codegen/error.rs @@ -103,10 +103,10 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { #[cfg(feature = "idl-build")] { - let idl_gen = gen_idl_print_function_for_error(&error); + let idl_build = gen_idl_print_function_for_error(&error); return quote! { #ret - #idl_gen + #idl_build }; }; diff --git a/lang/syn/src/codegen/program/mod.rs b/lang/syn/src/codegen/program/mod.rs index a519d1eabf..c8f96ae104 100644 --- a/lang/syn/src/codegen/program/mod.rs +++ b/lang/syn/src/codegen/program/mod.rs @@ -40,11 +40,11 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { #[cfg(feature = "idl-build")] { let no_docs = crate::idl::build::get_no_docs(); - let idl_gen = crate::idl::build::gen_idl_print_function_for_program(program, no_docs); + let idl_build = crate::idl::build::gen_idl_print_function_for_program(program, no_docs); return quote! { #ret - #idl_gen + #idl_build }; }; diff --git a/lang/syn/src/idl/build.rs b/lang/syn/src/idl/build.rs index db627594d1..1aea742b85 100644 --- a/lang/syn/src/idl/build.rs +++ b/lang/syn/src/idl/build.rs @@ -1,10 +1,39 @@ +pub use serde_json; + use crate::{parser::docs, AccountField, AccountsStruct, Error, Program}; use heck::MixedCase; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -pub use serde_json; use syn::{Ident, ItemEnum, ItemStruct}; +/// A trait that types must implement in order to generate the IDL via compilation. +/// +/// This trait is automatically implemented for Anchor all types that use the `AnchorSerialize` +/// proc macro. Note that manually implementing the `AnchorSerialize` trait will **NOT** have the +/// same effect. +/// +/// Types that don't implement this trait will cause a compile error during the IDL generation. +/// +/// The methods have default implementation that allows the program to compile but the type will +/// **NOT** be included in the IDL. +pub trait IdlBuild { + /// Returns the full module path of the type. + fn __anchor_private_full_path() -> String { + String::default() + } + + /// Returns the IDL type definition of the type or `None` if it doesn't exist. + fn __anchor_private_gen_idl_type() -> Option { + None + } + + /// Insert the type definition to the defined types hashmap. + fn __anchor_private_insert_idl_defined( + _defined_types: &mut std::collections::HashMap, + ) { + } +} + #[inline(always)] fn get_module_paths() -> (TokenStream, TokenStream) { ( @@ -439,7 +468,7 @@ pub fn idl_type_definition_ts_from_syn_enum( )) } -pub fn idl_gen_impl_skeleton( +pub fn idl_build_impl_skeleton( idl_type_definition_ts: TokenStream, insert_defined_ts: TokenStream, ident: &Ident, @@ -448,18 +477,19 @@ pub fn idl_gen_impl_skeleton( let (idl, _) = get_module_paths(); let name = ident.to_string(); let (impl_generics, ty_generics, where_clause) = input_generics.split_for_impl(); + let idl_build_trait = quote! {anchor_lang::anchor_syn::idl::build::IdlBuild}; quote! { - impl #impl_generics #ident #ty_generics #where_clause { - pub fn __anchor_private_full_path() -> String { + impl #impl_generics #idl_build_trait for #ident #ty_generics #where_clause { + fn __anchor_private_full_path() -> String { format!("{}::{}", std::module_path!(), #name) } - pub fn __anchor_private_gen_idl_type() -> Option<#idl::IdlTypeDefinition> { + fn __anchor_private_gen_idl_type() -> Option<#idl::IdlTypeDefinition> { #idl_type_definition_ts } - pub fn __anchor_private_insert_idl_defined( + fn __anchor_private_insert_idl_defined( defined_types: &mut std::collections::HashMap ) { #insert_defined_ts @@ -469,7 +499,7 @@ pub fn idl_gen_impl_skeleton( } // generates the IDL generation impl for for a struct -pub fn gen_idl_gen_impl_for_struct(strct: &ItemStruct, no_docs: bool) -> TokenStream { +pub fn gen_idl_build_impl_for_struct(strct: &ItemStruct, no_docs: bool) -> TokenStream { let idl_type_definition_ts: TokenStream; let insert_defined_ts: TokenStream; @@ -492,7 +522,7 @@ pub fn gen_idl_gen_impl_for_struct(strct: &ItemStruct, no_docs: bool) -> TokenSt let ident = &strct.ident; let input_generics = &strct.generics; - idl_gen_impl_skeleton( + idl_build_impl_skeleton( idl_type_definition_ts, insert_defined_ts, ident, @@ -501,7 +531,7 @@ pub fn gen_idl_gen_impl_for_struct(strct: &ItemStruct, no_docs: bool) -> TokenSt } // generates the IDL generation impl for for an enum -pub fn gen_idl_gen_impl_for_enum(enm: ItemEnum, no_docs: bool) -> TokenStream { +pub fn gen_idl_build_impl_for_enum(enm: ItemEnum, no_docs: bool) -> TokenStream { let idl_type_definition_ts: TokenStream; let insert_defined_ts: TokenStream; @@ -524,7 +554,7 @@ pub fn gen_idl_gen_impl_for_enum(enm: ItemEnum, no_docs: bool) -> TokenStream { let ident = &enm.ident; let input_generics = &enm.generics; - idl_gen_impl_skeleton( + idl_build_impl_skeleton( idl_type_definition_ts, insert_defined_ts, ident, @@ -533,7 +563,7 @@ pub fn gen_idl_gen_impl_for_enum(enm: ItemEnum, no_docs: bool) -> TokenStream { } // generates the IDL generation impl for for an event -pub fn gen_idl_gen_impl_for_event(event_strct: &ItemStruct) -> TokenStream { +pub fn gen_idl_build_impl_for_event(event_strct: &ItemStruct) -> TokenStream { fn parse_fields( fields: &syn::FieldsNamed, ) -> Result<(Vec, Vec), ()> { @@ -601,7 +631,7 @@ pub fn gen_idl_gen_impl_for_event(event_strct: &ItemStruct) -> TokenStream { } // generates the IDL generation impl for the Accounts struct -pub fn gen_idl_gen_impl_for_accounts_strct( +pub fn gen_idl_build_impl_for_accounts_struct( accs_strct: &AccountsStruct, no_docs: bool, ) -> TokenStream { @@ -817,7 +847,7 @@ pub fn gen_idl_print_function_for_event(event: &ItemStruct) -> TokenStream { let ident = &event.ident; let fn_name = format_ident!("__anchor_private_print_idl_event_{}", ident.to_string()); - let impl_gen = gen_idl_gen_impl_for_event(event); + let impl_gen = gen_idl_build_impl_for_event(event); quote! { #impl_gen diff --git a/spl/Cargo.toml b/spl/Cargo.toml index a52b45227a..5df1da9ba9 100644 --- a/spl/Cargo.toml +++ b/spl/Cargo.toml @@ -13,6 +13,7 @@ associated_token = ["spl-associated-token-account"] dex = ["serum_dex"] devnet = [] governance = [] +idl-build = ["anchor-lang/idl-build"] metadata = ["mpl-token-metadata"] mint = [] shmem = [] diff --git a/spl/src/governance.rs b/spl/src/governance.rs index 48ae908e6e..351351529d 100644 --- a/spl/src/governance.rs +++ b/spl/src/governance.rs @@ -54,5 +54,8 @@ macro_rules! vote_weight_record { &mut self.0 } } + + #[cfg(feature = "idl-build")] + impl anchor_lang::IdlBuild for VoterWeightRecord {} }; } diff --git a/spl/src/metadata.rs b/spl/src/metadata.rs index 9172b86dab..8ac8ea4ad2 100644 --- a/spl/src/metadata.rs +++ b/spl/src/metadata.rs @@ -782,6 +782,9 @@ impl Deref for MetadataAccount { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for MetadataAccount {} + #[derive(Clone, Debug, PartialEq)] pub struct MasterEditionAccount(mpl_token_metadata::state::MasterEditionV2); @@ -819,6 +822,9 @@ impl anchor_lang::Owner for MasterEditionAccount { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for MasterEditionAccount {} + #[derive(Clone, Debug, PartialEq)] pub struct TokenRecordAccount(mpl_token_metadata::state::TokenRecord); @@ -855,6 +861,9 @@ impl Deref for TokenRecordAccount { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for TokenRecordAccount {} + #[derive(Clone)] pub struct Metadata; diff --git a/spl/src/stake.rs b/spl/src/stake.rs index ce0659c48e..0bdc6d32ed 100644 --- a/spl/src/stake.rs +++ b/spl/src/stake.rs @@ -156,6 +156,9 @@ impl Deref for StakeAccount { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for StakeAccount {} + #[derive(Clone)] pub struct Stake; diff --git a/spl/src/token.rs b/spl/src/token.rs index fbb501cb9c..1a028cab94 100644 --- a/spl/src/token.rs +++ b/spl/src/token.rs @@ -486,6 +486,9 @@ impl Deref for TokenAccount { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for TokenAccount {} + #[derive(Clone, Debug, Default, PartialEq)] pub struct Mint(spl_token::state::Mint); @@ -517,6 +520,9 @@ impl Deref for Mint { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for Mint {} + #[derive(Clone)] pub struct Token; diff --git a/spl/src/token_interface.rs b/spl/src/token_interface.rs index 9b82a69ee4..59675e13aa 100644 --- a/spl/src/token_interface.rs +++ b/spl/src/token_interface.rs @@ -32,6 +32,9 @@ impl Deref for TokenAccount { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for TokenAccount {} + #[derive(Clone, Debug, Default, PartialEq)] pub struct Mint(spl_token_2022::state::Mint); @@ -59,6 +62,9 @@ impl Deref for Mint { } } +#[cfg(feature = "idl-build")] +impl anchor_lang::IdlBuild for Mint {} + #[derive(Clone)] pub struct TokenInterface; diff --git a/tests/idl/programs/idl/Cargo.toml b/tests/idl/programs/idl/Cargo.toml index 58a7400cd9..115780a1c5 100644 --- a/tests/idl/programs/idl/Cargo.toml +++ b/tests/idl/programs/idl/Cargo.toml @@ -13,11 +13,11 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] -idl-build = ["anchor-lang/idl-build", "external/idl-build"] +idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build", "external/idl-build"] default = [] [dependencies] anchor-lang = { path = "../../../../lang" } anchor-spl = { path = "../../../../spl" } -bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} +bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"] } external = { path = "../external", features = ["no-entrypoint"] } diff --git a/tests/idl/programs/idl/src/lib.rs b/tests/idl/programs/idl/src/lib.rs index 9f3e5a1707..8669005987 100644 --- a/tests/idl/programs/idl/src/lib.rs +++ b/tests/idl/programs/idl/src/lib.rs @@ -1,4 +1,5 @@ use anchor_lang::prelude::*; +use anchor_spl::{token, token_interface}; use std::str::FromStr; declare_id!("id11111111111111111111111111111111111111111"); @@ -273,6 +274,10 @@ pub struct Initialize<'info> { nested: NestedAccounts<'info>, zc_account: AccountLoader<'info, SomeZcAccount>, + token_account: Account<'info, token::TokenAccount>, + mint_account: Account<'info, token::Mint>, + token_interface_account: InterfaceAccount<'info, token_interface::TokenAccount>, + mint_interface_account: InterfaceAccount<'info, token_interface::Mint>, #[account(mut)] payer: Signer<'info>,