Skip to content
Open
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
59 changes: 53 additions & 6 deletions lang/syn/src/idl/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,55 @@ fn get_pda(acc: &Field, accounts: &AccountsStruct) -> TokenStream {
let parse_default = |expr: &syn::Expr| parse_seed(expr, accounts);

// Seeds
let seed_constraints = acc.constraints.seeds.as_ref();
// check for seeds in two places:
// 1. The standard `seeds = [...]` constraint.
// 2. The `init` constraint, which may contain `seeds` (e.g. `init, seeds = [...]`).
// This ensures we find the seeds regardless of how the user defined them.
let seed_constraints = acc.constraints.seeds.as_ref().or_else(|| {
acc.constraints
.init
.as_ref()
.and_then(|init| init.seeds.as_ref())
});

// Parse Seeds
let pda = seed_constraints

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsupported seeds::program expressions still fail silently,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch. The original change only handled unsupported expressions in the seeds list, but seeds::program still had a separate .ok()? path that could silently drop the PDA.

I’ve pushed a follow-up commit that handles program_seed parse failures explicitly and emits a warning before returning None, so unsupported seeds::program expressions are no longer silent.

.map(|seed| seed.seeds.iter().map(parse_default))
.and_then(|seeds| seeds.collect::<Result<Vec<_>>>().ok())
.and_then(|seed| {
// Try to parse every seed in the list.
// Collect into a Vec<Result> so we can inspect individual failures.
let results: Vec<Result<TokenStream, _>> =
seed.seeds.iter().map(parse_default).collect();

// CHECK FOR ERRORS:
// If `any` seed failed to parse (returns Err), it means the user used syntax that IDL doesn't support
if results.iter().any(|r| r.is_err()) {
warn_skipped_pda_seed(
acc,
"Seeds contain unsupported complex expressions (e.g., function calls)",
);

// Return None. This is safe; it simply omits the `pda` field from the JSON,
None
} else {
// If all seeds parsed correctly, unwrap them and return the vector.
Some(results.into_iter().map(|r| r.unwrap()).collect::<Vec<_>>())
}
})
.and_then(|seeds| {
let program = match seed_constraints {
Some(ConstraintSeedsGroup {
program_seed: Some(program),
..
}) => parse_default(program)
.map(|program| quote! { Some(#program) })
.ok()?,
}) => match parse_default(program) {
Ok(program) => quote! { Some(#program) },
Err(_) => {
warn_skipped_pda_seed(
acc,
"seeds::program contains unsupported complex expressions (e.g., function calls)",
);
return None;
}
},
_ => quote! { None },
};

Expand Down Expand Up @@ -276,6 +313,16 @@ fn get_pda(acc: &Field, accounts: &AccountsStruct) -> TokenStream {
quote! { None }
}

fn warn_skipped_pda_seed(acc: &Field, reason: &str) {
let name = acc.ident.to_string();
eprintln!(
"WARNING: Anchor IDL generation skipped for PDA seeds in account '{}'. \
Reason: {}. \
Workaround: Derive this PDA manually in your client.",
name, reason
);
}

/// Parse a seeds constraint, extracting the `IdlSeed` types.
///
/// Note: This implementation makes assumptions about the types that can be used (e.g., no
Expand Down