Skip to content

chore: more macro fixes#2183

Merged
ananas-block merged 4 commits intomainfrom
jorrit/more-macro-fixes
Jan 21, 2026
Merged

chore: more macro fixes#2183
ananas-block merged 4 commits intomainfrom
jorrit/more-macro-fixes

Conversation

@ananas-block
Copy link
Contributor

@ananas-block ananas-block commented Jan 20, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Macro attribute parse errors are now surfaced instead of being ignored.
    • Improved detection and clearer errors when multiple instruction parameters are present.
  • New Behavior

    • Account generation consistently resolves proof access whether provided directly or nested.
    • Field parsing accepts an explicit direct-proof hint when available, affecting default references.
  • Refactor

    • Unified extraction result representation to consolidate parsing and codegen flows.
  • Documentation

    • Clarified attribute syntax example.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Propagates direct CreateAccountsProof detection and proof-access expressions through Light PDAs macros; instruction-params extraction now returns an explicit ExtractResult; token-attribute parsing (extract_light_token_attr) now returns and propagates syn::Result errors instead of silently ignoring malformed syntax.

Changes

Cohort / File(s) Summary
Token attribute & seed extraction
sdk-libs/macros/src/light_pdas/account/seed_extraction.rs
extract_light_token_attr now returns syn::Result<Option<LightTokenAttr>>; callers (e.g., extract_from_accounts_struct) propagate parse errors with ? rather than swallowing malformed token attr syntax.
Proof-access helper & codegen usage
sdk-libs/macros/src/light_pdas/accounts/builder.rs
Adds get_proof_access to compute proof access (direct arg or params_ident.create_accounts_proof) and replaces ad-hoc proof-access logic across Light PDAs codegen paths.
Direct proof detection & parsing threading
sdk-libs/macros/src/light_pdas/accounts/light_account.rs, sdk-libs/macros/src/light_pdas/accounts/parse.rs
Detects CreateAccountsProof arg (find_direct_proof_arg) and threads direct_proof_arg: Option<Ident> through parse_light_account_attr, build_pda_field, build_mint_field; ParsedLightAccountsStruct gains direct_proof_arg.
Mint builder refactor
sdk-libs/macros/src/light_pdas/accounts/mint.rs
LightMintsBuilder now stores proof_access: &'a TokenStream (was params_ident: &'a Ident) and constructors/uses updated to reference proof_access.
Instruction extraction result
sdk-libs/macros/src/light_pdas/program/parsing.rs
Adds public ExtractResult enum (Success, MultipleParams, None) and changes extract_context_and_params to return it, enabling explicit multiple-params detection.
Instruction handling update
sdk-libs/macros/src/light_pdas/program/instructions.rs
Matches on ExtractResult from extract_context_and_params, handling Success, emitting errors for MultipleParams in rentfree contexts, and preserving prior wrapping for None.
Docs / Minor
sdk-libs/macros/src/light_pdas/shared_utils.rs
Small docs whitespace tweak in example attribute.

Sequence Diagram(s)

(omitted — changes are internal parsing/codegen updates that do not introduce a new multi-component runtime flow suitable for a sequence diagram)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

ai-review

Suggested reviewers

  • sergeytimoshin
  • SwenSchaeferjohann

Poem

✨ Proofs traced direct or nested through,
Parsers now surface errors when syntax is askew.
Builders, mints, and fields line up in tune,
Macros hum cleaner — clearer by noon.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'chore: more macro fixes' is vague and generic, using non-descriptive terms that don't convey meaningful information about the specific changes in the changeset. Consider a more specific title that highlights the primary changes, such as 'refactor: improve CreateAccountsProof handling and error propagation in macros' or 'refactor: extract direct proof argument type detection and threading'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 70.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jorrit/more-macro-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ananas-block ananas-block force-pushed the jorrit/more-macro-fixes branch from e463bb0 to 9e37eae Compare January 20, 2026 04:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk-libs/macros/src/light_pdas/accounts/light_account.rs (1)

732-741: Add coverage for the direct proof-arg default path.
The tests only exercise direct_proof_arg = None. Add at least one test that passes Some(proof_ident) to validate the new defaulting behavior for address_tree_info/output_tree.

🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/light_pdas/accounts/parse.rs`:
- Around line 248-251: Find and validate the direct-proof argument ordering:
after computing direct_proof_arg via find_direct_proof_arg(&instruction_args),
ensure that when direct_proof_arg.is_some() the referenced argument is the first
entry in instruction_args (otherwise get_proof_access will emit a `#proof_ident`
that isn't in the generated light_pre_init signature). If it is not first, emit
a clear compile-time error from parse.rs instructing the caller to make
CreateAccountsProof the first instruction arg (or update the generation logic),
or alternatively adjust the signature-generation code to include that arg;
reference find_direct_proof_arg, get_proof_access and the light_pre_init
signature when adding this validation/error.

In `@sdk-libs/macros/src/light_pdas/program/instructions.rs`:
- Line 536: The `use crate::light_pdas::program::parsing::ExtractResult;` import
is placed inside a for loop; move this `use` statement to the surrounding
function or module scope just above the `for` loop so the import is resolved
once rather than on every iteration, locating the import near the function that
contains the loop (referencing the `ExtractResult` type and the loop that
iterates over program instructions) to keep the code idiomatic and efficient.

In `@sdk-libs/macros/src/light_pdas/program/parsing.rs`:
- Around line 352-367: Add a Debug derive to the ExtractResult enum so macro
debugging can print extraction outcomes; update the enum declaration for
ExtractResult (which defines variants Success, MultipleParams, and None) to
include #[derive(Debug)] immediately above it.
- Around line 407-411: The current heuristic that filters parameter names using
!name.contains("signer") && !name.contains("bump") can false-negative legitimate
params (e.g., signer_count, bump_amount); update the filter in the params
collection logic (the code that pushes into params_candidates using pat_ident
and name) to use stricter checks such as exact matches and/or suffix/prefix
patterns instead of contains (for example check name == "signer" || name ==
"bump" or name.ends_with("_signer") || name.ends_with("_bump")), so only true
signer-like or bump-like identifiers are excluded while keeping other
identifiers like signer_count or bump_amount.
- Around line 749-768: Add a boundary test that verifies
extract_context_and_params returns ExtractResult::Success when there is exactly
one instruction parameter: create a new #[test] similar to the existing
multiple-params test but with a function signature pub fn handler(ctx:
Context<MyAccounts>, amount: u64) -> Result<()> { ... } and assert that
extract_context_and_params(&fn_item) matches ExtractResult::Success { .. };
reference the extract_context_and_params function and the ExtractResult::Success
variant when adding the test so it covers the single-parameter case.

Comment on lines +248 to +251
// Check if CreateAccountsProof is passed as a direct instruction argument
// (compute this early so we can use it for field parsing defaults)
let direct_proof_arg = find_direct_proof_arg(&instruction_args);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against direct-proof arg not being first (compile error risk).
get_proof_access() will emit #proof_ident even if that argument isn’t in the generated light_pre_init signature (only the first arg is passed). That causes an out-of-scope identifier when CreateAccountsProof isn’t the first instruction arg. Please enforce ordering (or adjust the generated signature).

🛠️ Suggested validation
     let direct_proof_arg = find_direct_proof_arg(&instruction_args);
+    if let (Some(proof_ident), Some(args)) = (&direct_proof_arg, &instruction_args) {
+        if args.first().map(|a| &a.name) != Some(proof_ident) {
+            return Err(Error::new_spanned(
+                proof_ident,
+                "CreateAccountsProof must be the first #[instruction] argument when passed directly; otherwise wrap it inside params.create_accounts_proof.",
+            ));
+        }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check if CreateAccountsProof is passed as a direct instruction argument
// (compute this early so we can use it for field parsing defaults)
let direct_proof_arg = find_direct_proof_arg(&instruction_args);
// Check if CreateAccountsProof is passed as a direct instruction argument
// (compute this early so we can use it for field parsing defaults)
let direct_proof_arg = find_direct_proof_arg(&instruction_args);
if let (Some(proof_ident), Some(args)) = (&direct_proof_arg, &instruction_args) {
if args.first().map(|a| &a.name) != Some(proof_ident) {
return Err(Error::new_spanned(
proof_ident,
"CreateAccountsProof must be the first #[instruction] argument when passed directly; otherwise wrap it inside params.create_accounts_proof.",
));
}
}
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/accounts/parse.rs` around lines 248 - 251,
Find and validate the direct-proof argument ordering: after computing
direct_proof_arg via find_direct_proof_arg(&instruction_args), ensure that when
direct_proof_arg.is_some() the referenced argument is the first entry in
instruction_args (otherwise get_proof_access will emit a `#proof_ident` that isn't
in the generated light_pre_init signature). If it is not first, emit a clear
compile-time error from parse.rs instructing the caller to make
CreateAccountsProof the first instruction arg (or update the generation logic),
or alternatively adjust the signature-generation code to include that arg;
reference find_direct_proof_arg, get_proof_access and the light_pre_init
signature when adding this validation/error.

if rentfree_struct_names.contains(&context_type) {
// Wrap the function with pre_init/finalize logic
*fn_item = wrap_function_with_light(fn_item, &params_ident, &ctx_name);
use crate::light_pdas::program::parsing::ExtractResult;
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Move the import outside the loop for efficiency.

The use statement is inside the for loop, causing it to be processed on every iteration. While Rust's name resolution handles this correctly, it's cleaner and more idiomatic to place imports at the block scope where they're needed—here, that means before the loop.

♻️ Suggested refactor
     // Auto-wrap instruction handlers that use rentfree Accounts structs
     if let Some((_, ref mut items)) = module.content {
+        use crate::light_pdas::program::parsing::ExtractResult;
         for item in items.iter_mut() {
             if let Item::Fn(fn_item) = item {
                 // Check if this function uses a rentfree Accounts struct
-                use crate::light_pdas::program::parsing::ExtractResult;
                 match extract_context_and_params(fn_item) {
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/program/instructions.rs` at line 536, The `use
crate::light_pdas::program::parsing::ExtractResult;` import is placed inside a
for loop; move this `use` statement to the surrounding function or module scope
just above the `for` loop so the import is resolved once rather than on every
iteration, locating the import near the function that contains the loop
(referencing the `ExtractResult` type and the loop that iterates over program
instructions) to keep the code idiomatic and efficient.

Comment on lines +352 to +367
/// Result from extracting context and params from a function signature.
pub enum ExtractResult {
/// Successfully extracted context type, params ident, and context ident
Success {
context_type: String,
params_ident: Ident,
ctx_ident: Ident,
},
/// Multiple params arguments detected (format-2 case) - caller decides if this is an error
MultipleParams {
context_type: String,
param_names: Vec<String>,
},
/// No valid context/params combination found
None,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Well-designed result enum for extraction outcomes.

The three-variant enum cleanly models all possible outcomes:

  • Success with full extraction data
  • MultipleParams for detecting format-2 cases that need consolidation
  • None for functions that don't match the expected signature

Consider adding #[derive(Debug)] to aid macro debugging, though this is optional for internal code.

🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/program/parsing.rs` around lines 352 - 367,
Add a Debug derive to the ExtractResult enum so macro debugging can print
extraction outcomes; update the enum declaration for ExtractResult (which
defines variants Success, MultipleParams, and None) to include #[derive(Debug)]
immediately above it.

Comment on lines 407 to 411
// Track potential params argument (not the context param, not signer-like names)
let name = pat_ident.ident.to_string();
if !name.contains("signer") && !name.contains("bump") {
// Prefer "params" but accept others
if name == "params" || params_ident.is_none() {
params_ident = Some(pat_ident.ident.clone());
}
params_candidates.push(pat_ident.ident.clone());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Filtering heuristic may have false negatives for edge-case parameter names.

The filter !name.contains("signer") && !name.contains("bump") would incorrectly exclude legitimate data parameters like signer_count, bump_amount, or token_signer_info. These substring matches might be overly broad.

Consider using exact matches or prefix/suffix patterns if this causes issues in practice:

// More precise filtering
let is_excluded = name == "signer" || name == "bump" 
    || name.ends_with("_signer") || name.ends_with("_bump");

That said, for typical Anchor patterns this works fine—flagging for awareness rather than immediate change.

🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/light_pdas/program/parsing.rs` around lines 407 - 411,
The current heuristic that filters parameter names using
!name.contains("signer") && !name.contains("bump") can false-negative legitimate
params (e.g., signer_count, bump_amount); update the filter in the params
collection logic (the code that pushes into params_candidates using pat_ident
and name) to use stricter checks such as exact matches and/or suffix/prefix
patterns instead of contains (for example check name == "signer" || name ==
"bump" or name.ends_with("_signer") || name.ends_with("_bump")), so only true
signer-like or bump-like identifiers are excluded while keeping other
identifiers like signer_count or bump_amount.

@ananas-block ananas-block force-pushed the jorrit/more-macro-fixes branch from ea9a6b1 to 3ba85c0 Compare January 21, 2026 19:03
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/light_pdas/accounts/light_account.rs`:
- Around line 740-741: Add a unit test that calls parse_light_account_attr with
the third parameter set to Some(ident) instead of &None to exercise the branch
where defaults should pick the direct proof identifier; specifically construct a
test input (field and ident) that relies on defaulting behavior, call
parse_light_account_attr(&field, &ident, &Some(ident.clone())), assert
result.is_ok(), and then assert the returned structure uses the provided direct
proof identifier in the expected fields (e.g., the direct_proof identifier slot
and any derived default names) to ensure the Some(...) path is covered and
behaves like the existing &None cases.

In `@sdk-libs/macros/src/light_pdas/accounts/parse.rs`:
- Around line 213-218: find_direct_proof_arg currently returns the first
matching CreateAccountsProof arg which makes selection ambiguous if multiple
matches exist; change it to detect multiple matches using
is_create_accounts_proof_type (e.g., collect all args where
is_create_accounts_proof_type(&arg.ty) is true) and, if more than one is found,
return/emit a compile error (use syn::Error::new_spanned or produce a
compile_error! token stream) instead of silently picking the first; update the
function signature and callers (e.g., any code expecting Option<Ident>) to
propagate the Result/syn::Error so the proc-macro fails with a clear message
when multiple CreateAccountsProof args are present.
♻️ Duplicate comments (3)
sdk-libs/macros/src/light_pdas/program/instructions.rs (1)

630-630: Move the import outside the loop for efficiency.

The use statement is still inside the for loop. While functionally correct, it's more idiomatic to place it at the block scope before the loop begins.

♻️ Suggested refactor
     // Auto-wrap instruction handlers that use rentfree Accounts structs
     if let Some((_, ref mut items)) = module.content {
+        use crate::light_pdas::program::parsing::ExtractResult;
         for item in items.iter_mut() {
             if let Item::Fn(fn_item) = item {
                 // Check if this function uses a rentfree Accounts struct
-                use crate::light_pdas::program::parsing::ExtractResult;
                 match extract_context_and_params(fn_item) {
sdk-libs/macros/src/light_pdas/accounts/parse.rs (1)

248-251: Validate direct CreateAccountsProof argument ordering to avoid out-of-scope identifiers.

direct_proof_arg is captured, but if that argument isn’t the first #[instruction] arg, the generated signature can omit it while codegen still emits #proof_ident, leading to a compile error. Enforce ordering or adjust the generated signature accordingly.

🛠️ Suggested validation
     let direct_proof_arg = find_direct_proof_arg(&instruction_args);
+    if let (Some(proof_ident), Some(args)) = (&direct_proof_arg, &instruction_args) {
+        if args.first().map(|a| &a.name) != Some(proof_ident) {
+            return Err(Error::new_spanned(
+                proof_ident,
+                "CreateAccountsProof must be the first #[instruction] argument when passed directly; otherwise wrap it inside params.create_accounts_proof.",
+            ));
+        }
+    }
sdk-libs/macros/src/light_pdas/program/parsing.rs (1)

409-412: Tighten the params filter to avoid false negatives.

contains("signer") / contains("bump") can exclude legitimate params like signer_count or bump_amount. Prefer exact or suffix/prefix checks.

♻️ Proposed refinement
-                if !name.contains("signer") && !name.contains("bump") {
+                let is_excluded = name == "signer"
+                    || name == "bump"
+                    || name.ends_with("_signer")
+                    || name.ends_with("_bump");
+                if !is_excluded {
                     params_candidates.push(pat_ident.ident.clone());
                 }

@ananas-block ananas-block merged commit 7c7a831 into main Jan 21, 2026
19 of 20 checks passed
@ananas-block ananas-block deleted the jorrit/more-macro-fixes branch January 21, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants