diff --git a/CHANGELOG.md b/CHANGELOG.md index 10196a3b4f..f57c43c605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The minor version will be incremented upon a breaking change and the patch versi ### Features +- lang: Allow to conditionally include attributes in the autogenerated instruction data structures ([#2963](https://github.com/coral-xyz/anchor/pull/2963)). - idl: Allow overriding the idl build toolchain with the `RUSTUP_TOOLCHAIN` environment variable ([#2941](https://github.com/coral-xyz/anchor/pull/2941])). - avm: Support customizing the installation location using `AVM_HOME` environment variable ([#2917](https://github.com/coral-xyz/anchor/pull/2917)). - idl, ts: Add accounts resolution for associated token accounts ([#2927](https://github.com/coral-xyz/anchor/pull/2927)). diff --git a/lang/syn/src/codegen/program/instruction.rs b/lang/syn/src/codegen/program/instruction.rs index 8e6ea4c487..7b389fcc92 100644 --- a/lang/syn/src/codegen/program/instruction.rs +++ b/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_cfg_attrs = &ix.cfg_attrs; let ix_name_camel = proc_macro2::Ident::new(&name.to_camel_case(), ix.raw_method.sig.ident.span()); let raw_args: Vec = ix @@ -43,6 +44,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { if ix.args.is_empty() { quote! { /// Instruction. + #(#ix_cfg_attrs)* #[derive(AnchorSerialize, AnchorDeserialize)] pub struct #ix_name_camel; @@ -51,6 +53,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { } else { quote! { /// Instruction. + #(#ix_cfg_attrs)* #[derive(AnchorSerialize, AnchorDeserialize)] pub struct #ix_name_camel { #(#raw_args),* diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index ad4a18399d..d5b0b186fb 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -26,8 +26,8 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; use syn::{ - Expr, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, LitInt, PatType, Token, Type, - TypePath, + Attribute, Expr, Generics, Ident, ItemEnum, ItemFn, ItemMod, ItemStruct, LitInt, PatType, + Token, Type, TypePath, }; #[derive(Debug)] @@ -63,6 +63,7 @@ pub struct Ix { pub raw_method: ItemFn, pub ident: Ident, pub docs: Option>, + pub cfg_attrs: Vec, pub args: Vec, pub returns: IxReturn, // The ident for the struct deriving Accounts. diff --git a/lang/syn/src/parser/program/instructions.rs b/lang/syn/src/parser/program/instructions.rs index b90ac8e1ee..e39f035b9d 100644 --- a/lang/syn/src/parser/program/instructions.rs +++ b/lang/syn/src/parser/program/instructions.rs @@ -2,8 +2,11 @@ use crate::parser::docs; use crate::parser::program::ctx_accounts_ident; use crate::parser::spl_interface; use crate::{FallbackFn, Ix, IxArg, IxReturn}; -use syn::parse::{Error as ParseError, Result as ParseResult}; -use syn::spanned::Spanned; +use syn::{ + parse::{Error as ParseError, Result as ParseResult}, + spanned::Spanned, + Attribute, +}; // Parse all non-state ix handlers from the program mod definition. pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<(Vec, Option)> { @@ -27,12 +30,14 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<(Vec, Option ParseResult { )), } } + +fn parse_cfg_attr(method: &syn::ItemFn) -> Vec { + method + .attrs + .iter() + .filter_map(|attr| match attr.path.is_ident("cfg_attr") { + true => Some(attr.to_owned()), + false => None, + }) + .collect() +} diff --git a/lang/syn/src/parser/program/mod.rs b/lang/syn/src/parser/program/mod.rs index 412d921d97..f45099121d 100644 --- a/lang/syn/src/parser/program/mod.rs +++ b/lang/syn/src/parser/program/mod.rs @@ -12,11 +12,31 @@ pub fn parse(program_mod: syn::ItemMod) -> ParseResult { ixs, name: program_mod.ident.clone(), docs, - program_mod, + program_mod: remove_cfg_attr_from_fns(program_mod), fallback_fn, }) } +fn remove_cfg_attr_from_fns(program_mod: syn::ItemMod) -> syn::ItemMod { + let mut program_mod = program_mod; + if let Some((brace, items)) = program_mod.content.as_mut() { + for item in items.iter_mut() { + if let syn::Item::Fn(item_fn) = item { + let new_attrs = item_fn + .attrs + .iter() + .filter(|attr| !attr.path.is_ident("cfg_attr")) + .cloned() + .collect(); + item_fn.attrs = new_attrs; + *item = syn::Item::Fn(item_fn.clone()); + } + } + program_mod.content = Some((*brace, items.to_vec())); + } + program_mod +} + fn ctx_accounts_ident(path_ty: &syn::PatType) -> ParseResult { let p = match &*path_ty.ty { syn::Type::Path(p) => &p.path, diff --git a/tests/misc/programs/misc/Cargo.toml b/tests/misc/programs/misc/Cargo.toml index 7b5a77c685..cbeaef0a97 100644 --- a/tests/misc/programs/misc/Cargo.toml +++ b/tests/misc/programs/misc/Cargo.toml @@ -10,11 +10,12 @@ crate-type = ["cdylib", "lib"] name = "misc" [features] +default = ["my-feature-derive"] no-entrypoint = [] no-idl = [] cpi = ["no-entrypoint"] -default = [] idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] +my-feature-derive = [] [dependencies] anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] } diff --git a/tests/misc/programs/misc/src/context.rs b/tests/misc/programs/misc/src/context.rs index d844aaae80..fe8f54938d 100644 --- a/tests/misc/programs/misc/src/context.rs +++ b/tests/misc/programs/misc/src/context.rs @@ -751,3 +751,6 @@ pub struct TestUsedIdentifiers<'info> { /// CHECK: ignore pub test4: AccountInfo<'info>, } + +#[derive(Accounts)] +pub struct EmptyWithCloneDerived {} diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index d0056018fe..7169ed3b00 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -385,4 +385,11 @@ pub mod misc { ) -> Result<()> { Ok(()) } + + #[cfg_attr(feature = "my-feature-derive", derive(Clone))] + pub fn test_derive_feature(_ctx: Context) -> Result<()> { + let ix_data = instruction::TestDeriveFeature {}; + let _ = ix_data.clone(); + Ok(()) + } }