Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions lang-v2/derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,32 @@ fn parse_associated_token_init(
}))
}

fn validate_associated_token_init_refs(
attrs: &AccountAttrs,
associated_token: Option<&AssociatedTokenInit>,
field_summaries: &[FieldSummary],
) -> syn::Result<()> {
let Some(at) = associated_token else {
return Ok(());
};
if !(attrs.is_init || attrs.is_init_if_needed) {
return Ok(());
}

for ident in [&at.mint, &at.authority, &at.token_program] {
if field_is_optional(field_summaries, ident) {
return Err(syn::Error::new(
ident.span(),
format!(
"`associated_token` constraints cannot reference optional account `{ident}` during init"
),
));
}
}

Ok(())
}

/// Wrap the `Result<Self>`-yielding `init_body` so that each runtime-only
/// namespaced constraint's `AccountConstraint::init` fires against the
/// freshly-typed value, then return the typed value. Producing this
Expand Down Expand Up @@ -2224,6 +2250,7 @@ pub fn parse_field(
}
let option_inner = extract_option_inner(field_ty);
let associated_token = parse_associated_token_init(&attrs, field_names)?;
validate_associated_token_init_refs(&attrs, associated_token.as_ref(), field_summaries)?;
let init_if_needed_reuse_validation = if attrs.is_init_if_needed {
Some(emit_init_if_needed_reuse_validation(
option_inner.unwrap_or(field_ty),
Expand Down
44 changes: 44 additions & 0 deletions lang-v2/tests/macro_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,50 @@ pub struct Bad {
);
}

#[test]
#[cfg_attr(
miri,
ignore = "spawns cargo and writes temporary workspaces; covered by normal cargo test"
)]
fn associated_token_init_rejects_optional_sibling_refs() {
compile_fail_case(
"associated_token_init_rejects_optional_sibling_refs",
r#"
use anchor_lang_v2::prelude::*;
use anchor_spl_v2::{
associated_token::AssociatedToken,
mint::Mint,
token::{Token, TokenAccount},
};

#[account]
pub struct Holder {
pub value: u64,
}

#[derive(Accounts)]
pub struct Bad {
#[account(mut)]
pub payer: Signer,
pub mint: Option<Account<Mint>>,
pub authority: Option<Account<Holder>>,
pub token_program: Program<Token>,
#[account(
init,
payer = payer,
associated_token::mint = mint,
associated_token::authority = authority,
associated_token::token_program = token_program,
)]
pub token_account: Account<TokenAccount>,
pub associated_token_program: Program<AssociatedToken>,
pub system_program: Program<System>,
}
"#,
&["associated_token` constraints cannot reference optional account"],
);
}

#[test]
#[cfg_attr(
miri,
Expand Down
Loading