lang: Allow the `cfg` attribute above the instructions (#2339)

This commit is contained in:
Jean Marchand (Exotic Markets) 2024-09-16 15:59:30 +02:00 committed by GitHub
parent 4866680cb5
commit e25918c609
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 50 additions and 6 deletions

View File

@ -72,6 +72,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- idl: Fix using `address` constraint with non-const expressions ([#3216](https://github.com/coral-xyz/anchor/pull/3216)). - idl: Fix using `address` constraint with non-const expressions ([#3216](https://github.com/coral-xyz/anchor/pull/3216)).
- idl: Fix using full path types with `Program` ([#3228](https://github.com/coral-xyz/anchor/pull/3228)). - idl: Fix using full path types with `Program` ([#3228](https://github.com/coral-xyz/anchor/pull/3228)).
- lang: Use closures for `init` constraints to reduce the stack usage of `try_accounts` ([#2939](https://github.com/coral-xyz/anchor/pull/2939)). - lang: Use closures for `init` constraints to reduce the stack usage of `try_accounts` ([#2939](https://github.com/coral-xyz/anchor/pull/2939)).
- lang: Allow the `cfg` attribute above the instructions ([#2339](https://github.com/coral-xyz/anchor/pull/2339)).
### Breaking ### Breaking

View File

@ -3,7 +3,7 @@ use heck::SnakeCase;
use quote::quote; use quote::quote;
pub fn generate(program: &Program) -> proc_macro2::TokenStream { pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let mut accounts = std::collections::HashSet::new(); let mut accounts = std::collections::HashMap::new();
// Go through instruction accounts. // Go through instruction accounts.
for ix in &program.ixs { for ix in &program.ixs {
@ -13,15 +13,16 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
"__client_accounts_{}", "__client_accounts_{}",
anchor_ident.to_string().to_snake_case() anchor_ident.to_string().to_snake_case()
); );
accounts.insert(macro_name); accounts.insert(macro_name, ix.cfgs.as_slice());
} }
// Build the tokens from all accounts // Build the tokens from all accounts
let account_structs: Vec<proc_macro2::TokenStream> = accounts let account_structs: Vec<proc_macro2::TokenStream> = accounts
.iter() .iter()
.map(|macro_name: &String| { .map(|(macro_name, cfgs)| {
let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap(); let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
quote! { quote! {
#(#cfgs)*
pub use crate::#macro_name::*; pub use crate::#macro_name::*;
} }
}) })

View File

@ -19,6 +19,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect(); let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
let discriminator = gen_discriminator(SIGHASH_GLOBAL_NAMESPACE, name); let discriminator = gen_discriminator(SIGHASH_GLOBAL_NAMESPACE, name);
let ret_type = &ix.returns.ty.to_token_stream(); let ret_type = &ix.returns.ty.to_token_stream();
let ix_cfgs = &ix.cfgs;
let (method_ret, maybe_return) = match ret_type.to_string().as_str() { let (method_ret, maybe_return) = match ret_type.to_string().as_str() {
"()" => (quote! {anchor_lang::Result<()> }, quote! { Ok(()) }), "()" => (quote! {anchor_lang::Result<()> }, quote! { Ok(()) }),
_ => ( _ => (
@ -28,6 +29,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}; };
quote! { quote! {
#(#ix_cfgs)*
pub fn #method_name<'a, 'b, 'c, 'info>( pub fn #method_name<'a, 'b, 'c, 'info>(
ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>, ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
#(#args),* #(#args),*
@ -91,7 +93,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
} }
pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream { pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
let mut accounts = std::collections::HashSet::new(); let mut accounts = std::collections::HashMap::new();
// Go through instruction accounts. // Go through instruction accounts.
for ix in &program.ixs { for ix in &program.ixs {
@ -101,15 +103,17 @@ pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
"__cpi_client_accounts_{}", "__cpi_client_accounts_{}",
anchor_ident.to_string().to_snake_case() anchor_ident.to_string().to_snake_case()
); );
accounts.insert(macro_name); let cfgs = &ix.cfgs;
accounts.insert(macro_name, cfgs.as_slice());
} }
// Build the tokens from all accounts // Build the tokens from all accounts
let account_structs: Vec<proc_macro2::TokenStream> = accounts let account_structs: Vec<proc_macro2::TokenStream> = accounts
.iter() .iter()
.map(|macro_name: &String| { .map(|(macro_name, cfgs)| {
let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap(); let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
quote! { quote! {
#(#cfgs)*
pub use crate::#macro_name::*; pub use crate::#macro_name::*;
} }
}) })

View File

@ -12,8 +12,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.parse() .parse()
.expect("Failed to parse ix method name in camel as `TokenStream`"); .expect("Failed to parse ix method name in camel as `TokenStream`");
let discriminator = quote! { instruction::#ix_name_camel::DISCRIMINATOR }; let discriminator = quote! { instruction::#ix_name_camel::DISCRIMINATOR };
let ix_cfgs = &ix.cfgs;
quote! { quote! {
#(#ix_cfgs)*
if data.starts_with(#discriminator) { if data.starts_with(#discriminator) {
return __private::__global::#ix_method_name( return __private::__global::#ix_method_name(
program_id, program_id,

View File

@ -104,6 +104,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args); let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
let ix_name_log = format!("Instruction: {ix_name}"); let ix_name_log = format!("Instruction: {ix_name}");
let ret_type = &ix.returns.ty.to_token_stream(); let ret_type = &ix.returns.ty.to_token_stream();
let cfgs = &ix.cfgs;
let maybe_set_return_data = match ret_type.to_string().as_str() { let maybe_set_return_data = match ret_type.to_string().as_str() {
"()" => quote! {}, "()" => quote! {},
_ => quote! { _ => quote! {
@ -113,6 +114,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}, },
}; };
quote! { quote! {
#(#cfgs)*
#[inline(never)] #[inline(never)]
pub fn #ix_method_name<'info>( pub fn #ix_method_name<'info>(
__program_id: &Pubkey, __program_id: &Pubkey,

View File

@ -10,6 +10,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.iter() .iter()
.map(|ix| { .map(|ix| {
let name = &ix.raw_method.sig.ident.to_string(); let name = &ix.raw_method.sig.ident.to_string();
let ix_cfgs = &ix.cfgs;
let ix_name_camel = let ix_name_camel =
proc_macro2::Ident::new(&name.to_camel_case(), ix.raw_method.sig.ident.span()); proc_macro2::Ident::new(&name.to_camel_case(), ix.raw_method.sig.ident.span());
let raw_args: Vec<proc_macro2::TokenStream> = ix let raw_args: Vec<proc_macro2::TokenStream> = ix
@ -34,10 +35,13 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
}; };
quote! { quote! {
#(#ix_cfgs)*
impl anchor_lang::Discriminator for #ix_name_camel { impl anchor_lang::Discriminator for #ix_name_camel {
const DISCRIMINATOR: &'static [u8] = #discriminator; const DISCRIMINATOR: &'static [u8] = #discriminator;
} }
#(#ix_cfgs)*
impl anchor_lang::InstructionData for #ix_name_camel {} impl anchor_lang::InstructionData for #ix_name_camel {}
#(#ix_cfgs)*
impl anchor_lang::Owner for #ix_name_camel { impl anchor_lang::Owner for #ix_name_camel {
fn owner() -> Pubkey { fn owner() -> Pubkey {
ID ID
@ -48,6 +52,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// If no args, output a "unit" variant instead of a struct variant. // If no args, output a "unit" variant instead of a struct variant.
if ix.args.is_empty() { if ix.args.is_empty() {
quote! { quote! {
#(#ix_cfgs)*
/// Instruction. /// Instruction.
#[derive(AnchorSerialize, AnchorDeserialize)] #[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #ix_name_camel; pub struct #ix_name_camel;
@ -56,6 +61,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
} }
} else { } else {
quote! { quote! {
#(#ix_cfgs)*
/// Instruction. /// Instruction.
#[derive(AnchorSerialize, AnchorDeserialize)] #[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #ix_name_camel { pub struct #ix_name_camel {

View File

@ -32,6 +32,7 @@ pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream {
let name = ix.ident.to_string(); let name = ix.ident.to_string();
let name_pascal = format_ident!("{}", name.to_camel_case()); let name_pascal = format_ident!("{}", name.to_camel_case());
let ctx_ident = &ix.anchor_ident; let ctx_ident = &ix.anchor_ident;
let cfgs = &ix.cfgs;
let docs = match &ix.docs { let docs = match &ix.docs {
Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] }, Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
@ -74,6 +75,7 @@ pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream {
Ok(( Ok((
quote! { quote! {
#(#cfgs)*
#idl::IdlInstruction { #idl::IdlInstruction {
name: #name.into(), name: #name.into(),
docs: #docs, docs: #docs,

View File

@ -25,6 +25,7 @@ use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult}
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::token::Comma; use syn::token::Comma;
use syn::Attribute;
use syn::Lit; use syn::Lit;
use syn::{ use syn::{
Expr, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, LitInt, PatType, Token, Type, Expr, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, LitInt, PatType, Token, Type,
@ -64,6 +65,7 @@ pub struct Ix {
pub raw_method: ItemFn, pub raw_method: ItemFn,
pub ident: Ident, pub ident: Ident,
pub docs: Option<Vec<String>>, pub docs: Option<Vec<String>>,
pub cfgs: Vec<Attribute>,
pub args: Vec<IxArg>, pub args: Vec<IxArg>,
pub returns: IxReturn, pub returns: IxReturn,
// The ident for the struct deriving Accounts. // The ident for the struct deriving Accounts.

View File

@ -4,6 +4,7 @@ use crate::parser::spl_interface;
use crate::{FallbackFn, Ix, IxArg, IxReturn, Overrides}; use crate::{FallbackFn, Ix, IxArg, IxReturn, Overrides};
use syn::parse::{Error as ParseError, Result as ParseResult}; use syn::parse::{Error as ParseError, Result as ParseResult};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::Attribute;
// Parse all non-state ix handlers from the program mod definition. // Parse all non-state ix handlers from the program mod definition.
pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<(Vec<Ix>, Option<FallbackFn>)> { pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<(Vec<Ix>, Option<FallbackFn>)> {
@ -28,12 +29,14 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<(Vec<Ix>, Option<Fallbac
let overrides = parse_overrides(&method.attrs)?; let overrides = parse_overrides(&method.attrs)?;
let interface_discriminator = spl_interface::parse(&method.attrs); let interface_discriminator = spl_interface::parse(&method.attrs);
let docs = docs::parse(&method.attrs); let docs = docs::parse(&method.attrs);
let cfgs = parse_cfg(method);
let returns = parse_return(method)?; let returns = parse_return(method)?;
let anchor_ident = ctx_accounts_ident(&ctx.raw_arg)?; let anchor_ident = ctx_accounts_ident(&ctx.raw_arg)?;
Ok(Ix { Ok(Ix {
raw_method: method.clone(), raw_method: method.clone(),
ident: method.sig.ident.clone(), ident: method.sig.ident.clone(),
docs, docs,
cfgs,
args, args,
anchor_ident, anchor_ident,
returns, returns,
@ -146,3 +149,14 @@ pub fn parse_return(method: &syn::ItemFn) -> ParseResult<IxReturn> {
)), )),
} }
} }
fn parse_cfg(method: &syn::ItemFn) -> Vec<Attribute> {
method
.attrs
.iter()
.filter_map(|attr| match attr.path.is_ident("cfg") {
true => Some(attr.to_owned()),
false => None,
})
.collect()
}

View File

@ -14,6 +14,7 @@ no-idl = []
cpi = ["no-entrypoint"] cpi = ["no-entrypoint"]
default = [] default = []
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
my-feature = []
[dependencies] [dependencies]
anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] } anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }

View File

@ -812,3 +812,7 @@ pub struct TestBoxedOwnerConstraint<'info> {
pub my_account: Box<Account<'info, Data>>, pub my_account: Box<Account<'info, Data>>,
pub program: Program<'info, Misc>, pub program: Program<'info, Misc>,
} }
#[cfg(feature = "my-feature")]
#[derive(Accounts)]
pub struct Empty {}

View File

@ -396,4 +396,9 @@ pub mod misc {
pub fn test_boxed_owner_constraint(_ctx: Context<TestBoxedOwnerConstraint>) -> Result<()> { pub fn test_boxed_owner_constraint(_ctx: Context<TestBoxedOwnerConstraint>) -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "my-feature")]
pub fn only_my_feature(_ctx: Context<Empty>) -> Result<()> {
Ok(())
}
} }