Explorar el Código

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

Jean Marchand (Exotic Markets) hace 1 año
padre
commit
e25918c609

+ 1 - 0
CHANGELOG.md

@@ -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 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: Allow the `cfg` attribute above the instructions ([#2339](https://github.com/coral-xyz/anchor/pull/2339)).
 
 ### Breaking
 

+ 4 - 3
lang/syn/src/codegen/program/accounts.rs

@@ -3,7 +3,7 @@ use heck::SnakeCase;
 use quote::quote;
 
 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.
     for ix in &program.ixs {
@@ -13,15 +13,16 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             "__client_accounts_{}",
             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
     let account_structs: Vec<proc_macro2::TokenStream> = accounts
         .iter()
-        .map(|macro_name: &String| {
+        .map(|(macro_name, cfgs)| {
             let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
             quote! {
+                #(#cfgs)*
                 pub use crate::#macro_name::*;
             }
         })

+ 7 - 3
lang/syn/src/codegen/program/cpi.rs

@@ -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 discriminator = gen_discriminator(SIGHASH_GLOBAL_NAMESPACE, name);
                 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() {
                     "()" => (quote! {anchor_lang::Result<()> }, quote! { Ok(()) }),
                     _ => (
@@ -28,6 +29,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 };
 
                 quote! {
+                    #(#ix_cfgs)*
                     pub fn #method_name<'a, 'b, 'c, 'info>(
                         ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
                         #(#args),*
@@ -91,7 +93,7 @@ pub fn generate(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.
     for ix in &program.ixs {
@@ -101,15 +103,17 @@ pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
             "__cpi_client_accounts_{}",
             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
     let account_structs: Vec<proc_macro2::TokenStream> = accounts
         .iter()
-        .map(|macro_name: &String| {
+        .map(|(macro_name, cfgs)| {
             let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
             quote! {
+                #(#cfgs)*
                 pub use crate::#macro_name::*;
             }
         })

+ 2 - 0
lang/syn/src/codegen/program/dispatch.rs

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

+ 2 - 0
lang/syn/src/codegen/program/handlers.rs

@@ -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 ix_name_log = format!("Instruction: {ix_name}");
             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() {
                 "()" => quote! {},
                 _ => quote! {
@@ -113,6 +114,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 },
             };
             quote! {
+                #(#cfgs)*
                 #[inline(never)]
                 pub fn #ix_method_name<'info>(
                     __program_id: &Pubkey,

+ 6 - 0
lang/syn/src/codegen/program/instruction.rs

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

+ 2 - 0
lang/syn/src/idl/program.rs

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

+ 2 - 0
lang/syn/src/lib.rs

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

+ 14 - 0
lang/syn/src/parser/program/instructions.rs

@@ -4,6 +4,7 @@ use crate::parser::spl_interface;
 use crate::{FallbackFn, Ix, IxArg, IxReturn, Overrides};
 use syn::parse::{Error as ParseError, Result as ParseResult};
 use syn::spanned::Spanned;
+use syn::Attribute;
 
 // Parse all non-state ix handlers from the program mod definition.
 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 interface_discriminator = spl_interface::parse(&method.attrs);
             let docs = docs::parse(&method.attrs);
+            let cfgs = parse_cfg(method);
             let returns = parse_return(method)?;
             let anchor_ident = ctx_accounts_ident(&ctx.raw_arg)?;
             Ok(Ix {
                 raw_method: method.clone(),
                 ident: method.sig.ident.clone(),
                 docs,
+                cfgs,
                 args,
                 anchor_ident,
                 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()
+}

+ 1 - 0
tests/misc/programs/misc/Cargo.toml

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

+ 4 - 0
tests/misc/programs/misc/src/context.rs

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

+ 5 - 0
tests/misc/programs/misc/src/lib.rs

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