Conversation
Replace brute-force file scanning with Anchor-style module following. CrateContext parses only declared modules via `mod xxx;` statements instead of scanning all .rs files recursively.
📝 WalkthroughWalkthroughThis PR removes the old Changes
Sequence Diagram(s)sequenceDiagram
participant Crate as Crate Source
participant Macro as rentfree_program macro (compile-time)
participant CrateCtx as CrateContext parser
participant Codegen as rentfree codegen
participant Generated as Generated runtime code (compress/decompress entrypoints)
participant Runtime as Program runtime / CPI / Mint
Crate->>Macro: expand #[rentfree_program] on module
Macro->>CrateCtx: parse crate files to discover `#[derive(RentFree)]` structs
CrateCtx-->>Macro: list of parsed RentFree structs and seed specs
Macro->>Codegen: build seed types, entrypoints, pre_init/finalize wrappers
Codegen-->>Generated: inject generated structs, impls, entrypoint functions
Generated->>Runtime: runtime compress/decompress entrypoint invoked (runtime call)
Runtime->>Runtime: perform CPI/mint actions, compute PDA seeds, update compression config
Runtime-->>Generated: return status / error
Generated-->>Macro: (compile-time) expansion completes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 24
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
sdk-libs/macros/src/rentfree/traits/traits.rs (1)
170-173: Consider defensive error handling for serialization.The
expect("Failed to serialize")will panic if Borsh serialization fails. While this is unlikely for well-formed compressible account types, it creates a panic path in generated code. If this is intentional (to fail fast on programmer error), a more descriptive message would help debugging:- + self.#field_name.try_to_vec().expect("Failed to serialize").len() + + self.#field_name.try_to_vec().expect("Failed to serialize field for size calculation - ensure type implements BorshSerialize correctly").len()Alternatively, if graceful degradation is preferred, consider returning a
Resultfrom thesize()method.sdk-libs/macros/src/rentfree/program/seed_codegen.rs (1)
371-376: String-based parameter deduplication is fragile.The check
p.to_string().contains(&field_name.to_string())could produce false positives. For example, iffield_nameis"id"and there's already a parameter likeuser_id: &Pubkey, the contains check would incorrectly skip adding theidparameter.Consider using a
HashSet<String>to track added parameter names explicitly:♻️ Suggested approach
// At function start, alongside parameters and expressions: let mut seen_params: std::collections::HashSet<String> = std::collections::HashSet::new(); // Then when adding parameters: let param_name = field_name.to_string(); if seen_params.insert(param_name) { parameters.push(param_with_ref); }
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/rentfree/accounts/light_mint.rs`:
- Around line 97-113: The default for address_tree_info is being hard-coded to
params.create_accounts_proof.address_tree_info which breaks if the handler's
params argument has a different identifier; change the logic so
args.address_tree_info is left as Option and move the unwrap_or_else/default
injection into codegen using the actual params identifier (params_ident) instead
of the literal "params"; specifically, stop calling .unwrap_or_else(||
syn::parse_quote!(params.create_accounts_proof.address_tree_info)) when
constructing the LightMintField and instead keep address_tree_info as
args.address_tree_info.map(Into::into) (or an Option) and emit the fallback
expression referencing `#params_ident.create_accounts_proof.address_tree_info`
during code generation for LightMintField construction (same fix for the other
occurrence around lines 194-200).
- Around line 139-143: The macro is incorrectly prefixing attribute expressions
with "self." causing duplications like "self.self.authority"; in
generate_freeze_authority_tokens (and the other similar blocks around lines
262-332) stop injecting a "self." before the parsed Expr and instead splice the
Expr verbatim into the quote macro (replace occurrences like
self.#freeze_auth.to_account_info() with `#freeze_auth.to_account_info`(), and
similarly remove the explicit "self." prefix in other helper functions/quotes
that interpolate MetaExpr/Exprs). Ensure you update all places that used the
self.#expr pattern to use `#expr` so attribute expressions are emitted exactly as
written.
- Around line 265-266: The line assigning __proof uses expect and can panic
on-chain; replace the expect-based unwrap on
`#params_ident.create_accounts_proof.proof.0.clone`() with a recoverable error by
converting the Option into a Result and returning a ProgramError (e.g.,
ProgramError::InvalidInstructionData) if missing, i.e. change the assignment to
use .ok_or(ProgramError::InvalidInstructionData)? (or map_err to another
appropriate ProgramError) so the function (which returns ProgramResult)
propagates an error instead of panicking.
In `@sdk-libs/macros/src/rentfree/accounts/pda.rs`:
- Around line 191-194: Detect and fail-fast when more than one #[light_mint] is
present: replace blind indexing of parsed.light_mint_fields[0] with a guard that
checks if parsed.light_mint_fields.len() > 1 and returns a syn::Error (e.g.,
syn::Error::new_spanned(...) using the extra field's span) indicating multiple
light_mint attributes are not supported yet; apply the same guard where the code
later accesses parsed.light_mint_fields[0] (the second occurrence) so both
codegen sites error instead of silently using only the first mint.
- Around line 134-165: The generated impls for light_pre_init and light_finalize
are producing unused-parameter warnings for the generated parameter identifier
(`#params_ident`); to fix, explicitly reference the parameter in each generated
body (e.g., add a no-op binding like let _ = `#params_ident`;) so the compiler
sees it used: modify the pre_init_body generation to include a reference to
`#params_ident` before returning Ok(false), and modify the finalize_body (or the
generated light_finalize function body) to reference `#params_ident` (and any
other unused params) before returning Ok(()); update the code that emits
pre_init_body and finalize_body so both include the no-op usage to suppress
warnings.
In `@sdk-libs/macros/src/rentfree/program/compress.rs`:
- Around line 147-154: The unreachable! message in
generate_compress_accounts_struct is too vague; update the panic text to clearly
state why this branch should never be hit and which variants are unsupported
(e.g., mention that PdaOnly and TokenOnly are not implemented for
compress_accounts_struct and that codegen should validate before calling).
Locate the match on InstructionVariant in generate_compress_accounts_struct and
replace the current unreachable! invocation with a more descriptive message
referencing the unsupported variants (PdaOnly, TokenOnly) and the function name
to aid debugging.
- Around line 123-141: The generated entrypoint compress_accounts_idempotent
declares a proof: light_sdk::instruction::ValidityProof parameter but never uses
it; either remove the proof parameter from compress_accounts_idempotent's
signature or forward it into the processor call; specifically update the
function signature in generate_compress_instruction_entrypoint (remove proof and
any callers that expect it) or modify the call to
__processor_functions::process_compress_accounts_idempotent to pass proof as an
additional argument (and update that processor function signature accordingly).
- Around line 205-223: The enum RentFreeInstructionError contains four
placeholder variants (CTokenDecompressionNotImplemented,
PdaDecompressionNotImplemented, TokenCompressionNotImplemented,
PdaCompressionNotImplemented); add clear TODO/issue-tracking markers so they’re
discoverable: update the RentFreeInstructionError definition to include either
inline comments above each variant (e.g., // TODO: implement — issue `#NNN` or
link) or amend the #[msg(...)] text to include an issue reference (e.g., "Not
implemented — issue `#NNN`"), ensuring each of the four variant symbols is
annotated with a unique TODO/issue identifier.
- Around line 44-53: The helper __anchor_to_program_error currently strips
non-custom Anchor errors to code 0; instead convert the Anchor error to the full
Solana ProgramError to preserve context: take the incoming E:
Into<anchor_lang::error::Error>, convert to anchor_lang::error::Error and then
to anchor_lang::prelude::ProgramError and finally return that ProgramError
converted into solana_program_error::ProgramError (e.g. use program_error.into()
rather than extracting only Custom(code)); update __anchor_to_program_error to
return the full converted error so both custom and non-custom Anchor errors
retain their original semantics.
In `@sdk-libs/macros/src/rentfree/program/crate_context.rs`:
- Around line 145-154: The code currently silently skips when an external module
file isn't found in the find_module_file call; update the block after the call
to emit a warning or debug log that includes the module name and parent path
(e.g., mod_name, child_path, root_dir/root_name) so typos are visible while
preserving behavior for cfg'd-out modules; use the project's logging facility
(e.g., log::warn or tracing::warn) and keep the recursive parse via
Self::parse_recursive and modules.extend unchanged.
- Around line 180-219: The function find_module_file currently duplicates
existence checks for the same `paths` vector (checked inside the `if parent_name
== "mod" || parent_name == "lib"` branch and again at the end); simplify by
consolidating the lookup: compute `paths` and, when parent is not "mod"/"lib",
compute `extra_paths` using `parent_mod_dir` and `parent_name`, then iterate
once over `paths.iter().chain(extra_paths.iter())` (or just `paths` when
`extra_paths` is empty) to check `.exists()` and return the first match; update
the branches so `paths`/`extra_paths` are built but checked only once to remove
the redundant loops.
In `@sdk-libs/macros/src/rentfree/program/decompress.rs`:
- Around line 159-223: The function
generate_pda_seed_derivation_for_trait_with_ctx_seeds duplicates seed handling
already in seed_utils.rs (seed_element_to_ref_expr); extract a shared helper in
seed_utils.rs that takes a SeedElement (or syn::Expr) plus a context-aware
mapper (or an Option<&HashSet<String>> like ctx_field_names) and returns the
reference expression and optional binding info, then replace the duplicated
branches in generate_pda_seed_derivation_for_trait_with_ctx_seeds to call that
helper (still applying transform_expr_for_ctx_seeds where needed), preserving
the existing binding naming scheme (seed_{i}) and the final seeds/seeds_vec
construction so behavior and outputs remain unchanged.
- Around line 127-140: Replace the two hardcoded program addresses in the
generated accounts with constants from light-sdk-types: change the address
attribute on the light_token_program field to use LIGHT_TOKEN_PROGRAM_ID and
change the address on ctoken_cpi_authority to use LIGHT_TOKEN_CPI_AUTHORITY (or
export LIGHT_TOKEN_CPI_AUTHORITY from light-sdk-types if it currently lives in
token-sdk), i.e., update the #[account(address = "...")] on the
light_token_program and ctoken_cpi_authority fields to #[account(address =
light_sdk_types::constants::LIGHT_TOKEN_PROGRAM_ID)] and #[account(address =
light_sdk_types::constants::LIGHT_TOKEN_CPI_AUTHORITY)] (or import those
constants into the macro scope), and remove the hardcoded string literals so the
macro depends on the shared constants instead of duplicating addresses.
In `@sdk-libs/macros/src/rentfree/program/instructions.rs`:
- Around line 90-93: The generated unit struct SeedParams (created in
seed_params_struct in instructions.rs) is empty and may confuse readers; either
remove its generation if it is unused, or keep it but add a clear doc comment
explaining its role and why it is empty (e.g., a reserved placeholder for future
seed fields or marker type). If you remove it, also remove any references to
SeedParams elsewhere; if you keep it, modify the quote! to include a doc comment
(/// ...) above pub struct SeedParams to clarify intent.
- Line 48: The codegen function currently does let content =
module.content.as_mut().unwrap(); which can panic if module.content is None;
replace this unwrap with a defensive check in codegen: use if let Some(content)
= module.content.as_mut() { ... } and return a clear error (or propagate a
Result) when content is None (e.g., return Err(...) with a helpful message), so
codegen no longer panics unexpectedly; reference symbols: module.content and the
codegen function (and keep compatibility with rentfree_program_impl’s checks).
In `@sdk-libs/macros/src/rentfree/program/mod.rs`:
- Line 17: The project declares pub mod visitors in rentfree::program but the
file visitors.rs is missing, causing build failures because parsing.rs,
seed_utils.rs, and variant_enum.rs reference super::visitors::FieldExtractor;
either add a new sdk-libs/macros/src/rentfree/program/visitors.rs implementing
and exporting FieldExtractor (matching the expected API used by parsing::*,
seed_utils::*, and variant_enum::*) or remove the pub mod visitors declaration
and inline or refactor references to FieldExtractor in parsing.rs,
seed_utils.rs, and variant_enum.rs to use an alternative implementation; ensure
the signatures and visibility match what those files expect so the compiler can
resolve super::visitors::FieldExtractor.
In `@sdk-libs/macros/src/rentfree/program/parsing.rs`:
- Line 10: The import super::visitors::FieldExtractor in parsing.rs is
unresolved; fix it by either exporting/adding the visitors module that defines
FieldExtractor or by updating the import to the correct module path where
FieldExtractor is actually declared (e.g., change
super::visitors::FieldExtractor to the real module name or re-export
FieldExtractor from visitors); locate the reference to FieldExtractor in
parsing.rs and ensure the module containing FieldExtractor is declared pub mod
visitors (or contains a pub use) so the symbol resolves.
- Around line 346-426: The wrapper currently hardcodes the Context parameter
name "ctx" in wrap_function_with_rentfree while extract_context_and_params
already discovers parameters; modify extract_context_and_params to also return
the Ident for the Context parameter (e.g., return Option<(String, Ident, Ident)>
or rename to return ctx_ident), then update wrap_function_with_rentfree to
accept that ctx_ident and use it wherever "ctx" is referenced (replace all
occurrences in the generated code: ctx.accounts..., ctx.remaining_accounts,
etc.) so the generated code uses the actual context variable name from the
original fn signature instead of the literal "ctx".
In `@sdk-libs/macros/src/rentfree/program/seed_codegen.rs`:
- Around line 183-193: The conditional that builds function params/body using
auth_parameters and generate_seed_derivation_body treats program id
asymmetrically: when auth_parameters.is_empty() you accept _program_id:
&solana_pubkey::Pubkey and derive with it, but when parameters exist you
hardcode &crate::ID; add a concise inline comment above this block (next to
auth_parameters, generate_seed_derivation_body, auth_seed_expressions) that
explains the intended rationale for this difference (or, if the asymmetry is
accidental, change the branch to consistently use the same program id source),
so future maintainers understand why _program_id is used in one case and
&crate::ID in the other.
In `@sdk-libs/macros/src/rentfree/program/seed_utils.rs`:
- Around line 85-88: The call to
super::visitors::FieldExtractor::ctx_fields(&[]).extract(expr) in
extract_ctx_field_name fails because the visitors module (and FieldExtractor) is
missing; either add a visitors.rs implementing FieldExtractor with a ctx_fields
constructor and an extract method that returns Option<Ident>, or reimplement
extract_ctx_field_name to parse the syn::Expr directly (e.g., match on
Expr::Path/Expr::Field, walk the path segments and return the first Ident that
represents the ctx field) without using the visitor pattern; update the function
extract_ctx_field_name to call the new FieldExtractor or the direct parsing
logic and ensure signatures/types match (Ident return) so the module compiles.
In `@sdk-libs/macros/src/rentfree/program/variant_enum.rs`:
- Around line 346-365: The code in extract_ctx_fields_from_token_spec references
super::visitors::FieldExtractor which doesn't resolve; update the module path or
add a re-export so both parsing.rs and variant_enum.rs can access
FieldExtractor. Locate the FieldExtractor type (likely defined in
crate::visitors or in the visitors module sibling) and either change the call to
the correct absolute/relative path (e.g., crate::visitors::FieldExtractor or
crate::parser::visitors::FieldExtractor) or add a pub use
visitors::FieldExtractor in the parent module so super::visitors::FieldExtractor
resolves; ensure the same fix is applied consistently where parsing.rs
referenced FieldExtractor.
In `@sdk-libs/macros/src/rentfree/README.md`:
- Around line 5-58: Update the README module listing to include relative links
to each key file and directory so readers can jump to definitions; for each
bullet (e.g., "accounts/", "program/", "traits/") add markdown links to the
corresponding files like accounts/mod.rs, accounts/parse.rs,
accounts/codegen.rs, program/mod.rs, program/instructions.rs,
program/seed_providers.rs, traits/mod.rs, traits/traits.rs,
traits/pack_unpack.rs, etc., and optionally include line anchors for important
symbols (e.g., derive_rentfree(), rentfree_program_impl(),
RentFreeAccountVariant) so clicking takes the reader directly to
implementations.
- Around line 7-29: Update the README's top-level fenced tree block to include a
language specifier (e.g., change the opening "```" to "```text") so the
directory tree is correctly recognized and MD040 is avoided; edit the fenced
block in the rentfree README (the directory tree block beginning on line 7) to
use "```text" and keep the rest of the block unchanged.
In `@sdk-libs/macros/src/utils.rs`:
- Around line 34-47: The current to_snake_case implementation in utils.rs
intentionally inserts underscores before every uppercase (e.g., HTTPServer ->
h_t_t_p_server); keep this implementation for now (do not refactor) but add a
short comment above pub(crate) fn to_snake_case explaining this deliberate
behavior and noting that acronym-aware conversion can be introduced later if
needed; also add a brief unit test (e.g., in the same module) demonstrating
current behavior for a typical CamelCase input like "CTokenAccount" to lock
expectations used by instructions.rs.
| // address_tree_info defaults to params.create_accounts_proof.address_tree_info | ||
| let address_tree_info = args | ||
| .address_tree_info | ||
| .map(Into::into) | ||
| .unwrap_or_else(|| syn::parse_quote!(params.create_accounts_proof.address_tree_info)); | ||
|
|
||
| return Ok(Some(LightMintField { | ||
| field_ident: field_ident.clone(), | ||
| mint_signer: args.mint_signer.into(), | ||
| authority: args.authority.into(), | ||
| decimals: args.decimals.into(), | ||
| address_tree_info, | ||
| freeze_authority: args.freeze_authority.map(Into::into), | ||
| signer_seeds: args.signer_seeds.map(Into::into), | ||
| rent_payment: args.rent_payment.map(Into::into), | ||
| write_top_up: args.write_top_up.map(Into::into), | ||
| })); |
There was a problem hiding this comment.
Default address_tree_info hard-codes params
If the handler names its params argument something else (which you already extract as params_ident), the generated code will not compile. Defer the default until codegen so you can reference #params_ident, or enforce the name during parsing.
🛠️ Proposed fix (defer default to codegen)
pub(super) struct LightMintField {
/// The field name where #[light_mint] is attached (CMint account)
pub field_ident: Ident,
@@
/// Address tree info expression
- pub address_tree_info: Expr,
+ pub address_tree_info: Option<Expr>,
@@
- let address_tree_info = args
- .address_tree_info
- .map(Into::into)
- .unwrap_or_else(|| syn::parse_quote!(params.create_accounts_proof.address_tree_info));
+ let address_tree_info = args.address_tree_info.map(Into::into);
@@
- let address_tree_info = &mint.address_tree_info;
+ let address_tree_info = mint
+ .address_tree_info
+ .clone()
+ .unwrap_or_else(|| syn::parse_quote!(`#params_ident.create_accounts_proof.address_tree_info`));Also applies to: 194-200
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/light_mint.rs` around lines 97 - 113,
The default for address_tree_info is being hard-coded to
params.create_accounts_proof.address_tree_info which breaks if the handler's
params argument has a different identifier; change the logic so
args.address_tree_info is left as Option and move the unwrap_or_else/default
injection into codegen using the actual params identifier (params_ident) instead
of the literal "params"; specifically, stop calling .unwrap_or_else(||
syn::parse_quote!(params.create_accounts_proof.address_tree_info)) when
constructing the LightMintField and instead keep address_tree_info as
args.address_tree_info.map(Into::into) (or an Option) and emit the fallback
expression referencing `#params_ident.create_accounts_proof.address_tree_info`
during code generation for LightMintField construction (same fix for the other
occurrence around lines 194-200).
| fn generate_freeze_authority_tokens(freeze_authority: &Option<Expr>) -> TokenStream { | ||
| if let Some(freeze_auth) = freeze_authority { | ||
| quote! { Some(*self.#freeze_auth.to_account_info().key) } | ||
| } else { | ||
| quote! { None } |
There was a problem hiding this comment.
Attribute expressions are double-prefixed with self.
MetaExpr accepts arbitrary expressions (e.g., self.authority), but codegen emits self.#expr, which becomes self.self.authority or self.(ctx.accounts.x) and fails to compile. Either require idents at parse-time or use the expression verbatim (recommended).
🛠️ Proposed fix (use expressions as-is)
- quote! { Some(*self.#freeze_auth.to_account_info().key) }
+ quote! { Some(*((`#freeze_auth`).to_account_info().key)) }
- let mint_signer_key = self.#mint_signer.to_account_info().key;
+ let mint_signer_key = (`#mint_signer`).to_account_info().key;
- mint_authority: Some((*self.#authority.to_account_info().key).to_bytes().into()),
+ mint_authority: Some((*((`#authority`).to_account_info().key)).to_bytes().into()),
- account_infos.push(self.#authority.to_account_info());
- account_infos.push(self.#mint_signer.to_account_info());
+ account_infos.push((`#authority`).to_account_info());
+ account_infos.push((`#mint_signer`).to_account_info());Also applies to: 262-332
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/light_mint.rs` around lines 139 - 143,
The macro is incorrectly prefixing attribute expressions with "self." causing
duplications like "self.self.authority"; in generate_freeze_authority_tokens
(and the other similar blocks around lines 262-332) stop injecting a "self."
before the parsed Expr and instead splice the Expr verbatim into the quote macro
(replace occurrences like self.#freeze_auth.to_account_info() with
`#freeze_auth.to_account_info`(), and similarly remove the explicit "self." prefix
in other helper functions/quotes that interpolate MetaExpr/Exprs). Ensure you
update all places that used the self.#expr pattern to use `#expr` so attribute
expressions are emitted exactly as written.
| let __proof: light_token_sdk::CompressedProof = #params_ident.create_accounts_proof.proof.0.clone() | ||
| .expect("proof is required for mint creation"); |
There was a problem hiding this comment.
Replace expect with a recoverable error
Panics on-chain are costly and opaque. If proof is missing, return a ProgramError instead of aborting.
🛠️ Proposed fix
- let __proof: light_token_sdk::CompressedProof = `#params_ident.create_accounts_proof.proof.0.clone`()
- .expect("proof is required for mint creation");
+ let __proof: light_token_sdk::CompressedProof = `#params_ident`
+ .create_accounts_proof
+ .proof
+ .0
+ .clone()
+ .ok_or(solana_program_error::ProgramError::InvalidInstructionData)?;🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/light_mint.rs` around lines 265 - 266,
The line assigning __proof uses expect and can panic on-chain; replace the
expect-based unwrap on `#params_ident.create_accounts_proof.proof.0.clone`() with
a recoverable error by converting the Option into a Result and returning a
ProgramError (e.g., ProgramError::InvalidInstructionData) if missing, i.e.
change the assignment to use .ok_or(ProgramError::InvalidInstructionData)? (or
map_err to another appropriate ProgramError) so the function (which returns
ProgramResult) propagates an error instead of panicking.
| } else { | ||
| quote! { Ok(false) } | ||
| }; | ||
|
|
||
| // LightFinalize: No-op (all work done in pre_init) | ||
| let finalize_body = quote! { Ok(()) }; | ||
|
|
||
| Ok(quote! { | ||
| #[automatically_derived] | ||
| impl #impl_generics light_sdk::compressible::LightPreInit<'info, #params_type> for #struct_name #ty_generics #where_clause { | ||
| fn light_pre_init( | ||
| &mut self, | ||
| _remaining: &[solana_account_info::AccountInfo<'info>], | ||
| #params_ident: &#params_type, | ||
| ) -> std::result::Result<bool, light_sdk::error::LightSdkError> { | ||
| use anchor_lang::ToAccountInfo; | ||
| #pre_init_body | ||
| } | ||
| } | ||
|
|
||
| #[automatically_derived] | ||
| impl #impl_generics light_sdk::compressible::LightFinalize<'info, #params_type> for #struct_name #ty_generics #where_clause { | ||
| fn light_finalize( | ||
| &mut self, | ||
| _remaining: &[solana_account_info::AccountInfo<'info>], | ||
| #params_ident: &#params_type, | ||
| _has_pre_init: bool, | ||
| ) -> std::result::Result<(), light_sdk::error::LightSdkError> { | ||
| use anchor_lang::ToAccountInfo; | ||
| #finalize_body | ||
| } | ||
| } |
There was a problem hiding this comment.
Avoid unused‑parameter warnings in generated impls.
Lines 134‑136 return Ok(false) without using #params_ident, and Lines 154‑165 never use it in LightFinalize. This produces unused‑parameter warnings in generated code; with -D warnings in SDK tests, that becomes a build failure. Based on learnings, ...
🐛 Proposed fix
- } else {
- quote! { Ok(false) }
- };
+ } else {
+ quote! { let _ = `#params_ident`; Ok(false) }
+ };
@@
- let finalize_body = quote! { Ok(()) };
+ let finalize_body = quote! {{ let _ = `#params_ident`; Ok(()) }};📝 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.
| } else { | |
| quote! { Ok(false) } | |
| }; | |
| // LightFinalize: No-op (all work done in pre_init) | |
| let finalize_body = quote! { Ok(()) }; | |
| Ok(quote! { | |
| #[automatically_derived] | |
| impl #impl_generics light_sdk::compressible::LightPreInit<'info, #params_type> for #struct_name #ty_generics #where_clause { | |
| fn light_pre_init( | |
| &mut self, | |
| _remaining: &[solana_account_info::AccountInfo<'info>], | |
| #params_ident: &#params_type, | |
| ) -> std::result::Result<bool, light_sdk::error::LightSdkError> { | |
| use anchor_lang::ToAccountInfo; | |
| #pre_init_body | |
| } | |
| } | |
| #[automatically_derived] | |
| impl #impl_generics light_sdk::compressible::LightFinalize<'info, #params_type> for #struct_name #ty_generics #where_clause { | |
| fn light_finalize( | |
| &mut self, | |
| _remaining: &[solana_account_info::AccountInfo<'info>], | |
| #params_ident: &#params_type, | |
| _has_pre_init: bool, | |
| ) -> std::result::Result<(), light_sdk::error::LightSdkError> { | |
| use anchor_lang::ToAccountInfo; | |
| #finalize_body | |
| } | |
| } | |
| } else { | |
| quote! { let _ = `#params_ident`; Ok(false) } | |
| }; | |
| // LightFinalize: No-op (all work done in pre_init) | |
| let finalize_body = quote! {{ let _ = `#params_ident`; Ok(()) }}; | |
| Ok(quote! { | |
| #[automatically_derived] | |
| impl `#impl_generics` light_sdk::compressible::LightPreInit<'info, `#params_type`> for `#struct_name` `#ty_generics` `#where_clause` { | |
| fn light_pre_init( | |
| &mut self, | |
| _remaining: &[solana_account_info::AccountInfo<'info>], | |
| `#params_ident`: &#params_type, | |
| ) -> std::result::Result<bool, light_sdk::error::LightSdkError> { | |
| use anchor_lang::ToAccountInfo; | |
| `#pre_init_body` | |
| } | |
| } | |
| #[automatically_derived] | |
| impl `#impl_generics` light_sdk::compressible::LightFinalize<'info, `#params_type`> for `#struct_name` `#ty_generics` `#where_clause` { | |
| fn light_finalize( | |
| &mut self, | |
| _remaining: &[solana_account_info::AccountInfo<'info>], | |
| `#params_ident`: &#params_type, | |
| _has_pre_init: bool, | |
| ) -> std::result::Result<(), light_sdk::error::LightSdkError> { | |
| use anchor_lang::ToAccountInfo; | |
| `#finalize_body` | |
| } | |
| } | |
| }) |
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/pda.rs` around lines 134 - 165, The
generated impls for light_pre_init and light_finalize are producing
unused-parameter warnings for the generated parameter identifier
(`#params_ident`); to fix, explicitly reference the parameter in each generated
body (e.g., add a no-op binding like let _ = `#params_ident`;) so the compiler
sees it used: modify the pre_init_body generation to include a reference to
`#params_ident` before returning Ok(false), and modify the finalize_body (or the
generated light_finalize function body) to reference `#params_ident` (and any
other unused params) before returning Ok(()); update the code that emits
pre_init_body and finalize_body so both include the no-op usage to suppress
warnings.
| // TODO(diff-pr): Support multiple #[light_mint] fields by looping here. | ||
| // Each mint would get assigned_account_index = pda_count + mint_index. | ||
| // Also add support for #[rentfree_token] fields for token ATAs. | ||
| let mint = &parsed.light_mint_fields[0]; |
There was a problem hiding this comment.
Fail fast when multiple #[light_mint] fields are present.
Lines 191‑194 and 268‑271 always select the first mint, silently ignoring extras. Until multi‑mint support lands, emit a syn::Error when more than one mint is parsed to avoid partial codegen.
🛑 Suggested guard
if total_accounts > 255 {
return Err(syn::Error::new_spanned(
struct_name,
format!(
@@
));
}
+ if parsed.light_mint_fields.len() > 1 {
+ return Err(syn::Error::new_spanned(
+ struct_name,
+ "Multiple #[light_mint] fields are not supported yet; please use a single mint.",
+ ));
+ }Also applies to: 268-271
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/pda.rs` around lines 191 - 194, Detect
and fail-fast when more than one #[light_mint] is present: replace blind
indexing of parsed.light_mint_fields[0] with a guard that checks if
parsed.light_mint_fields.len() > 1 and returns a syn::Error (e.g.,
syn::Error::new_spanned(...) using the extra field's span) indicating multiple
light_mint attributes are not supported yet; apply the same guard where the code
later accesses parsed.light_mint_fields[0] (the second occurrence) so both
codegen sites error instead of silently using only the first mint.
| fn extract_ctx_field_name(expr: &syn::Expr) -> Option<Ident> { | ||
| let fields = super::visitors::FieldExtractor::ctx_fields(&[]).extract(expr); | ||
| fields.into_iter().next() | ||
| } |
There was a problem hiding this comment.
Unresolved FieldExtractor in visitors module causes build failure.
This code references super::visitors::FieldExtractor::ctx_fields(&[]).extract(expr), but the visitors module doesn't exist (as noted in the mod.rs review). This causes a compilation error:
failed to resolve: could not find `FieldExtractor` in `visitors`
This needs to be resolved together with the missing visitors.rs file. Either:
- Add the
visitors.rsfile containingFieldExtractor, or - Implement
extract_ctx_field_namedifferently without relying on the visitor pattern
🧰 Tools
🪛 GitHub Check: Test program-libs-fast
[failure] 86-86:
failed to resolve: could not find FieldExtractor in visitors
[failure] 86-86:
failed to resolve: could not find FieldExtractor in visitors
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/seed_utils.rs` around lines 85 - 88, The
call to super::visitors::FieldExtractor::ctx_fields(&[]).extract(expr) in
extract_ctx_field_name fails because the visitors module (and FieldExtractor) is
missing; either add a visitors.rs implementing FieldExtractor with a ctx_fields
constructor and an extract method that returns Option<Ident>, or reimplement
extract_ctx_field_name to parse the syn::Expr directly (e.g., match on
Expr::Path/Expr::Field, walk the path segments and return the first Ident that
represents the ctx field) without using the visitor pattern; update the function
extract_ctx_field_name to call the new FieldExtractor or the direct parsing
logic and ensure signatures/types match (Ident return) so the module compiles.
| pub fn extract_ctx_fields_from_token_spec(spec: &TokenSeedSpec) -> Vec<Ident> { | ||
| const EXCLUDED: &[&str] = &[ | ||
| "fee_payer", | ||
| "rent_sponsor", | ||
| "config", | ||
| "compression_authority", | ||
| ]; | ||
|
|
||
| let mut all_fields = Vec::new(); | ||
| let mut seen = std::collections::HashSet::new(); | ||
|
|
||
| for seed in spec.seeds.iter().chain(spec.authority.iter().flatten()) { | ||
| if let SeedElement::Expression(expr) = seed { | ||
| // Extract fields from this expression using the visitor | ||
| let fields = super::visitors::FieldExtractor::ctx_fields(EXCLUDED).extract(expr); | ||
| // Deduplicate across seeds | ||
| for field in fields { | ||
| let name = field.to_string(); | ||
| if seen.insert(name) { | ||
| all_fields.push(field); |
There was a problem hiding this comment.
FieldExtractor path doesn’t resolve here either
This call site also references super::visitors::FieldExtractor, which CI reports as missing. Please fix the module path or re-export so both parsing.rs and variant_enum.rs build.
🧰 Tools
🪛 GitHub Check: Test program-libs-fast
[failure] 360-360:
failed to resolve: could not find FieldExtractor in visitors
[failure] 360-360:
failed to resolve: could not find FieldExtractor in visitors
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/variant_enum.rs` around lines 346 - 365,
The code in extract_ctx_fields_from_token_spec references
super::visitors::FieldExtractor which doesn't resolve; update the module path or
add a re-export so both parsing.rs and variant_enum.rs can access
FieldExtractor. Locate the FieldExtractor type (likely defined in
crate::visitors or in the visitors module sibling) and either change the call to
the correct absolute/relative path (e.g., crate::visitors::FieldExtractor or
crate::parser::visitors::FieldExtractor) or add a pub use
visitors::FieldExtractor in the parent module so super::visitors::FieldExtractor
resolves; ensure the same fix is applied consistently where parsing.rs
referenced FieldExtractor.
| ## Directory Structure | ||
|
|
||
| ``` | ||
| rentfree/ | ||
| ├── mod.rs # Module declaration | ||
| ├── README.md # This file | ||
| ├── accounts/ # #[derive(RentFree)] implementation | ||
| │ ├── mod.rs # Entry point: derive_rentfree() | ||
| │ ├── parse.rs # Parsing #[rentfree], #[light_mint] attributes | ||
| │ └── codegen.rs # LightPreInit/LightFinalize trait generation | ||
| ├── program/ # #[rentfree_program] implementation | ||
| │ ├── mod.rs # Entry point: rentfree_program_impl() | ||
| │ ├── instructions.rs # Instruction generation and handler wrapping | ||
| │ ├── crate_context.rs # Crate scanning for #[derive(Accounts)] structs | ||
| │ ├── variant_enum.rs # RentFreeAccountVariant enum generation | ||
| │ └── seed_providers.rs # PDA/CToken seed derivation implementations | ||
| └── traits/ # Shared trait derive macros | ||
| ├── mod.rs # Module declaration | ||
| ├── traits.rs # HasCompressionInfo, CompressAs, Compressible | ||
| ├── pack_unpack.rs # Pack/Unpack trait implementations | ||
| ├── light_compressible.rs # RentFreeAccount combined derive | ||
| ├── anchor_seeds.rs # Seed extraction from Anchor attributes | ||
| ├── decompress_context.rs # DecompressContext trait generation | ||
| └── utils.rs # Shared utility functions | ||
| ``` | ||
|
|
||
| ## Modules | ||
|
|
||
| ### `accounts/` - RentFree Derive Macro | ||
|
|
||
| Implements `#[derive(RentFree)]` for Anchor Accounts structs: | ||
|
|
||
| - **parse.rs** - Parses `#[rentfree]`, `#[rentfree_token]`, `#[light_mint]` attributes | ||
| - **codegen.rs** - Generates `LightPreInit` and `LightFinalize` trait implementations | ||
|
|
||
| ### `program/` - RentFree Program Macro | ||
|
|
||
| Implements `#[rentfree_program]` attribute macro: | ||
|
|
||
| - **instructions.rs** - Main macro logic, generates compress/decompress handlers | ||
| - **crate_context.rs** - Scans crate for `#[derive(Accounts)]` structs | ||
| - **variant_enum.rs** - Generates `RentFreeAccountVariant` enum with all traits | ||
| - **seed_providers.rs** - PDA and CToken seed provider implementations | ||
|
|
||
| ### `traits/` - Shared Trait Derives | ||
|
|
||
| Core trait implementations shared across macros: | ||
|
|
||
| - **traits.rs** - `HasCompressionInfo`, `CompressAs`, `Compressible` derives | ||
| - **pack_unpack.rs** - Generates `PackedXxx` structs, `Pack`/`Unpack` traits | ||
| - **light_compressible.rs** - `RentFreeAccount` combined derive macro | ||
| - **anchor_seeds.rs** - Extracts seeds from `#[account(seeds = [...])]` | ||
| - **decompress_context.rs** - `DecompressContext` trait generation | ||
| - **utils.rs** - Shared utilities (e.g., empty CToken enum generation) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Add relative links to key modules for discoverability.
The module listing is helpful; consider linking each entry to its file (relative path, optional line anchors) so readers can jump directly to definitions. Based on learnings, ...
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
7-7: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/README.md` around lines 5 - 58, Update the
README module listing to include relative links to each key file and directory
so readers can jump to definitions; for each bullet (e.g., "accounts/",
"program/", "traits/") add markdown links to the corresponding files like
accounts/mod.rs, accounts/parse.rs, accounts/codegen.rs, program/mod.rs,
program/instructions.rs, program/seed_providers.rs, traits/mod.rs,
traits/traits.rs, traits/pack_unpack.rs, etc., and optionally include line
anchors for important symbols (e.g., derive_rentfree(), rentfree_program_impl(),
RentFreeAccountVariant) so clicking takes the reader directly to
implementations.
| ``` | ||
| rentfree/ | ||
| ├── mod.rs # Module declaration | ||
| ├── README.md # This file | ||
| ├── accounts/ # #[derive(RentFree)] implementation | ||
| │ ├── mod.rs # Entry point: derive_rentfree() | ||
| │ ├── parse.rs # Parsing #[rentfree], #[light_mint] attributes | ||
| │ └── codegen.rs # LightPreInit/LightFinalize trait generation | ||
| ├── program/ # #[rentfree_program] implementation | ||
| │ ├── mod.rs # Entry point: rentfree_program_impl() | ||
| │ ├── instructions.rs # Instruction generation and handler wrapping | ||
| │ ├── crate_context.rs # Crate scanning for #[derive(Accounts)] structs | ||
| │ ├── variant_enum.rs # RentFreeAccountVariant enum generation | ||
| │ └── seed_providers.rs # PDA/CToken seed derivation implementations | ||
| └── traits/ # Shared trait derive macros | ||
| ├── mod.rs # Module declaration | ||
| ├── traits.rs # HasCompressionInfo, CompressAs, Compressible | ||
| ├── pack_unpack.rs # Pack/Unpack trait implementations | ||
| ├── light_compressible.rs # RentFreeAccount combined derive | ||
| ├── anchor_seeds.rs # Seed extraction from Anchor attributes | ||
| ├── decompress_context.rs # DecompressContext trait generation | ||
| └── utils.rs # Shared utility functions | ||
| ``` |
There was a problem hiding this comment.
Specify a language for the fenced tree block.
Line 7 starts a fenced block without a language, which triggers MD040 and hampers tooling.
📝 Suggested fix
-```
+```text
rentfree/
├── mod.rs # Module declaration
├── README.md # This file
@@
-```
+```🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
7-7: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/README.md` around lines 7 - 29, Update the
README's top-level fenced tree block to include a language specifier (e.g.,
change the opening "```" to "```text") so the directory tree is correctly
recognized and MD040 is avoided; edit the fenced block in the rentfree README
(the directory tree block beginning on line 7) to use "```text" and keep the
rest of the block unchanged.
| /// Convert PascalCase/CamelCase to snake_case (e.g., UserRecord -> user_record) | ||
| pub(crate) fn to_snake_case(s: &str) -> String { | ||
| let mut result = String::new(); | ||
| for (i, c) in s.chars().enumerate() { | ||
| if c.is_uppercase() { | ||
| if i > 0 { | ||
| result.push('_'); | ||
| } | ||
| result.push(c.to_ascii_lowercase()); | ||
| } else { | ||
| result.push(c); | ||
| } | ||
| } | ||
| result |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
# Find the to_snake_case function and verify its current implementation
fd "utils.rs" -g "sdk-libs/macros/*" -exec cat -n {} \;Repository: Lightprotocol/light-protocol
Length of output: 302
🏁 Script executed:
# Find all call sites of to_snake_case
rg "to_snake_case\(" -n --type rs -g "sdk-libs/**/*.rs"Repository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Check if there are tests for to_snake_case
fd "*.rs" -g "sdk-libs/macros/**" -exec grep -l "to_snake_case\|test" {} \;Repository: Lightprotocol/light-protocol
Length of output: 391
🏁 Script executed:
# Look at the broader context of the macros file to understand what identifiers are being processed
fd "*.rs" -g "sdk-libs/macros/src/**" -type f | head -20Repository: Lightprotocol/light-protocol
Length of output: 242
🏁 Script executed:
# First, explore the directory structure to find the utils.rs file
find sdk-libs/macros -name "utils.rs" -type fRepository: Lightprotocol/light-protocol
Length of output: 145
🏁 Script executed:
# Once found, read the file to see the actual implementation
cat sdk-libs/macros/src/utils.rsRepository: Lightprotocol/light-protocol
Length of output: 1498
🏁 Script executed:
# Search for all calls to to_snake_case in the codebase
rg "to_snake_case" --type-list | grep -i rustRepository: Lightprotocol/light-protocol
Length of output: 461
🏁 Script executed:
# Simpler approach - search for to_snake_case usage
rg "to_snake_case" sdk-libs/Repository: Lightprotocol/light-protocol
Length of output: 614
🏁 Script executed:
# Get more context around the to_snake_case usage in instructions.rs
rg -B 5 -A 5 "to_snake_case" sdk-libs/macros/src/rentfree/program/instructions.rsRepository: Lightprotocol/light-protocol
Length of output: 954
🏁 Script executed:
# Check if there are any test files or examples showing what identifiers are expected
find sdk-libs/macros -name "*.rs" | xargs grep -l "test\|HTTPServer\|PDA\|SDK" | head -5Repository: Lightprotocol/light-protocol
Length of output: 291
🏁 Script executed:
# Look at what types/structs are actually being processed in the macros
rg "struct|enum" sdk-libs/macros/src/rentfree/program/instructions.rs | head -20Repository: Lightprotocol/light-protocol
Length of output: 1515
🏁 Script executed:
# Check for real usage examples - see what struct names exist in the codebase
rg "struct [A-Z][A-Z]+" sdk-libs --max-count=20Repository: Lightprotocol/light-protocol
Length of output: 308
🏁 Script executed:
# Check the actual tests for the utils module
find sdk-libs/macros -name "*.rs" -exec grep -l "test.*to_snake_case\|to_snake_case.*test" {} \;Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at what variant names are actually used in the instruction definitions
rg "variant" sdk-libs/macros/src/rentfree/program/ -A 2 -B 2 | head -50Repository: Lightprotocol/light-protocol
Length of output: 4406
🏁 Script executed:
# Search for real struct/enum names with consecutive capitals in the codebase
rg "struct [A-Z]{2,}" sdk-libs/ --max-count=30Repository: Lightprotocol/light-protocol
Length of output: 308
🏁 Script executed:
# Check if there's a CLAUDE.md or similar documentation mentioned in tone instructions
find . -name "CLAUDE.md" -o -name "claude.md"Repository: Lightprotocol/light-protocol
Length of output: 511
🏁 Script executed:
# Look at actual test files in the macros directory
find sdk-libs/macros -name "*test*" -o -name "*tests*"Repository: Lightprotocol/light-protocol
Length of output: 93
🏁 Script executed:
# Look at the tests directory for to_snake_case tests
ls -la sdk-libs/macros/tests/Repository: Lightprotocol/light-protocol
Length of output: 369
🏁 Script executed:
# Check the tests that exist
find sdk-libs/macros/tests -name "*.rs" -exec cat {} \;Repository: Lightprotocol/light-protocol
Length of output: 50057
🏁 Script executed:
# Look at the main CLAUDE.md for guidance
head -100 CLAUDE.mdRepository: Lightprotocol/light-protocol
Length of output: 5665
🏁 Script executed:
# Check if there's documentation in sdk-libs/macros CLAUDE.md
find sdk-libs/macros -name "CLAUDE.md" -exec cat {} \;Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Let's also check the actual variant names being used in test fixtures or examples
rg "variant" sdk-libs/macros/fuzz -A 2 -B 2 | head -80Repository: Lightprotocol/light-protocol
Length of output: 542
🏁 Script executed:
# Check the camel_to_snake_case function in seed_codegen.rs to see if it's different
grep -A 15 "fn camel_to_snake_case" sdk-libs/macros/src/rentfree/program/seed_codegen.rsRepository: Lightprotocol/light-protocol
Length of output: 346
🏁 Script executed:
# Check if there are any actual variant names with consecutive capitals
rg "variant.*:" sdk-libs/macros/fuzz -A 1 -B 1 | head -40Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Search for real struct/enum variants used in the instruction macros
rg "enum.*Variant\|struct.*Variant" sdk-libs/macros/src/ -A 5 | head -50Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Let's look for actual variant names in the macro test fixtures
cat sdk-libs/macros/fuzz/fuzz_targets/struct_generation.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 3947
🏁 Script executed:
# Check the actual instruction macro usage to see what kinds of variant names are used
rg "variant_name\|VariantName" sdk-libs/macros/src/rentfree -B 2 -A 2 | head -60Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Search for any type names with consecutive capitals that would trigger the acronym issue
rg "struct [A-Z]{2,}|enum [A-Z]{2,}" sdk-libs -A 3 | head -40Repository: Lightprotocol/light-protocol
Length of output: 1055
🏁 Script executed:
# Final verification: check if there are any actual variant names with consecutive capitals anywhere
rg "enum\s+\w*[A-Z]{2,}" sdk-libs/macros -A 10 | head -60Repository: Lightprotocol/light-protocol
Length of output: 956
🏁 Script executed:
# Check the actual usage more carefully - where is the constructor_name used?
rg -B 10 "format_ident.*constructor_name\|constructor_name.*format_ident" sdk-libs/macros/src/rentfree/program/instructions.rsRepository: Lightprotocol/light-protocol
Length of output: 54
Consider deferring to_snake_case refactor—no acronym-heavy identifiers in current usage.
The implementation does insert underscores before every uppercase character (e.g., HTTPServer → h_t_t_p_server), which is theoretically suboptimal. However, variant names in the codebase follow standard CamelCase (e.g., CTokenAccount, TokenAccountMeta) without acronym runs. The proposed fix is sound for future-proofing, but current call sites in instructions.rs don't exhibit the problem. No test coverage exists, suggesting this hasn't been a practical issue.
If acronym-heavy identifiers become common, revisit with the improved algorithm. For now, this is a defensible design choice rather than a defect.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/utils.rs` around lines 34 - 47, The current to_snake_case
implementation in utils.rs intentionally inserts underscores before every
uppercase (e.g., HTTPServer -> h_t_t_p_server); keep this implementation for now
(do not refactor) but add a short comment above pub(crate) fn to_snake_case
explaining this deliberate behavior and noting that acronym-aware conversion can
be introduced later if needed; also add a brief unit test (e.g., in the same
module) demonstrating current behavior for a typical CamelCase input like
"CTokenAccount" to lock expectations used by instructions.rs.
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In `@sdk-libs/macros/src/rentfree/accounts/mod.rs`:
- Line 20: The TODO comment "TODO: add a codegen file that puts the generated
code together" in the rentfree accounts module should not remain untracked;
either remove the stray TODO and implement a minimal codegen aggregator scaffold
(e.g., export a generated mod and a public function that wires generated
artifacts) or replace the comment with a reference to a tracking issue/PR
number; update the module (mod.rs) to include the scaffold's placeholder
function or add the issue URL/ID in the comment so the intended work is tracked
and no orphan TODO remains.
In `@sdk-libs/macros/src/rentfree/accounts/parse.rs`:
- Around line 244-246: Add a new RentFreeTokenField AST and parser for the
#[rentfree_token(...)] attribute: define RentFreeTokenField with field_ident,
authority_seeds and mint_field_ref, add parsing logic in parse.rs alongside
existing rentfree field parsers (look for the current
RentFreeField/RentFreeAccount parsing functions) to recognize the attribute,
extract the authority_seeds list and the mint field reference, and populate the
new struct; also extend the decompression handler to accept both PDA and ctoken
account types by emitting the appropriate enum/variant for token PDAs vs ctoken
accounts so downstream code can handle both cases (create a parse_rentfree_token
or extend the existing attribute parsing switch to call the new parser and
register RentFreeTokenField).
- Around line 147-187: The macro currently infers the fee payer account by name
in parse.rs (match on field_name, setting fee_payer_field) which leads to poor
errors for differently named fields; update the field parsing logic in the same
file to first scan each field's attributes for an explicit #[fee_payer] marker
(when found set fee_payer_field = Some(field_ident.clone()) and prefer it over
the name-based match), keep the existing name-based fallback for backward
compatibility, and adjust any related diagnostic/error messaging to suggest
using #[fee_payer] when no fee payer is found; ensure unique symbols referenced
are the field attribute parser, fee_payer_field, field_ident and the name-based
match block.
In `@sdk-libs/macros/src/rentfree/program/crate_context.rs`:
- Around line 131-139: The inline module handling in the block that creates a
ParsedModule (using ParsedModule, inserting into modules via modules.insert)
does not recurse into nested inline `mod` items, so structs() will miss types
inside nested inline modules; add a helper (e.g., parse_inline) that mirrors
parse_recursive but iterates the provided `items` AST, recursively processes any
nested `Item::Mod` inline content, and registers each nested ParsedModule into
the same modules map (or call the new helper from where inline modules are
constructed) so nested structs are discovered the same way as external modules.
In `@sdk-libs/macros/src/rentfree/program/decompress.rs`:
- Around line 98-106: The match in generate_decompress_accounts_struct currently
uses unreachable!() for InstructionVariant::PdaOnly and ::TokenOnly which can
panic at macro expansion time; change this to return an Err with a descriptive
syn::Error (or the crate's preferred Error type) instead of panicking. Locate
generate_decompress_accounts_struct and replace the unreachable! branch for
PdaOnly and TokenOnly with a return Err(...) that includes a clear message like
"decompress_accounts_struct only supports Mixed variant" and uses
syn::Error::new_spanned or equivalent so the caller receives a proper
compile-time error rather than a panic.
In `@sdk-libs/macros/src/rentfree/program/expr_traversal.rs`:
- Around line 63-70: The Expr::Call arm currently transforms only call_expr.args
but leaves call_expr.func unchanged, so update the Expr::Call handler in
transform_expr_for_ctx_seeds to recursively transform the callee as well: apply
transform_expr_for_ctx_seeds to call_expr.func (similar to how the
Expr::MethodCall receiver is handled), assign the transformed func back into
new_call.func, and then transform args as already implemented; also add a unit
test covering a call whose callee is a field access like data.seed_fn() or
ctx.accounts.get_seed() to ensure the callee transformation is applied.
In `@sdk-libs/macros/src/rentfree/program/visitors.rs`:
- Around line 92-99: The helper function is_ctx_accounts is currently public but
only used internally by classify_field_expr and map_call_arg; change its
visibility to restrict external access (make it private or pub(crate)) by
updating the signature of is_ctx_accounts to remove the pub modifier (or use
pub(crate) if module-wide access is needed), keeping its implementation and
references in classify_field_expr and map_call_arg unchanged so callers still
resolve; ensure no external code relies on is_ctx_accounts before finalizing the
visibility change.
In `@sdk-libs/macros/src/rentfree/traits/mod.rs`:
- Around line 1-9: Update the module-level documentation comment at the top of
the file to use "rentfree accounts" instead of "compressible accounts" and/or
add an explicit note about compatibility with legacy compressible terminology;
specifically edit the module docstring that lists `seed_extraction`,
`decompress_context`, `light_compressible`, `pack_unpack`, `traits`, and `utils`
so the first line and description reflect "rentfree accounts" (or state that
these macros remain compatible with compressible accounts) to avoid confusion in
public docs.
In `@sdk-libs/macros/src/rentfree/traits/traits.rs`:
- Around line 25-43: The current FromMeta impl for CompressAsFields only
implements from_list and rejects marker-only attributes; to support marker-only
`#[compress_as]` add a from_word() implementation that returns
Ok(CompressAsFields { fields: Vec::new() }) so the parser accepts the bare
attribute, keeping the existing from_list logic in place; implement this as a
FromMeta::from_word method on CompressAsFields so marker-only usage is parsed
while existing field overrides via from_list still work.
♻️ Duplicate comments (10)
sdk-libs/macros/src/rentfree/program/seed_codegen.rs (1)
174-193: Clarify or normalizeprogram_idhandling for authority seed helpers.The helper uses caller-provided
program_idonly when there are no parameters, but hardcodes&crate::IDotherwise. That asymmetry can surprise client callers—either make it consistent or document the rationale inline.🧭 Suggested clarification (inline comments)
- let (fn_params, fn_body) = if auth_parameters.is_empty() { + let (fn_params, fn_body) = if auth_parameters.is_empty() { + // No params: allow caller to supply program_id for flexibility ( quote! { _program_id: &solana_pubkey::Pubkey }, generate_seed_derivation_body( &auth_seed_expressions, quote! { _program_id }, ), ) } else { + // Params present: derive using crate::ID to bind to this program ( quote! { #(`#auth_parameters`),* }, generate_seed_derivation_body( &auth_seed_expressions, quote! { &crate::ID }, ), ) };sdk-libs/macros/src/rentfree/accounts/light_mint.rs (3)
97-101: Don’t hard-codeparamsin the defaultaddress_tree_info.The default expression uses
params.create_accounts_proof..., but the handler’s params identifier can differ. That will emit invalid code for non-paramsnames. Defer the default until codegen so you can reference#params_ident.🛠️ Proposed fix (defer default to codegen)
pub(super) struct LightMintField { @@ - pub address_tree_info: Expr, + pub address_tree_info: Option<Expr>, @@ - let address_tree_info = args.address_tree_info.map(Into::into).unwrap_or_else(|| { - syn::parse_quote!(params.create_accounts_proof.address_tree_info) - }); + let address_tree_info = args.address_tree_info.map(Into::into); @@ - let address_tree_info = &mint.address_tree_info; + let address_tree_info = mint + .address_tree_info + .clone() + .unwrap_or_else(|| syn::parse_quote!(`#params_ident.create_accounts_proof.address_tree_info`));
137-143: Use MetaExpr verbatim; avoidself.#exprdouble-prefixing.
MetaExprallows arbitrary expressions (e.g.,self.authority,ctx.accounts.x). Emittingself.#exprturns those into invalidself.self.authorityorself.(ctx...). Splice the expression directly (or restrict the parser to idents).🛠️ Sample fix (apply to all `self.#expr` occurrences)
- quote! { Some(*self.#freeze_auth.to_account_info().key) } + quote! { Some(*((`#freeze_auth`).to_account_info().key)) } @@ - let mint_signer_key = self.#mint_signer.to_account_info().key; + let mint_signer_key = (`#mint_signer`).to_account_info().key; @@ - mint_authority: Some((*self.#authority.to_account_info().key).to_bytes().into()), + mint_authority: Some((*((`#authority`).to_account_info().key)).to_bytes().into()), @@ - account_infos.push(self.#authority.to_account_info()); - account_infos.push(self.#mint_signer.to_account_info()); + account_infos.push((`#authority`).to_account_info()); + account_infos.push((`#mint_signer`).to_account_info());Also applies to: 261-331
264-265: Don’t panic on missing proof; return a ProgramError.
expectwill abort the program and lose the error context on-chain. Convert theOptioninto a recoverable error.🛠️ Proposed fix
- let __proof: light_token_sdk::CompressedProof = `#params_ident.create_accounts_proof.proof.0.clone`() - .expect("proof is required for mint creation"); + let __proof: light_token_sdk::CompressedProof = `#params_ident` + .create_accounts_proof + .proof + .0 + .clone() + .ok_or(solana_program_error::ProgramError::InvalidInstructionData)?;sdk-libs/macros/src/rentfree/accounts/pda.rs (2)
141-145: Suppress unused-parameter warnings in generated impls.When no PDAs/mints are present,
#params_identis unused inlight_pre_init, andlight_finalizenever uses it. With warnings-as-errors, that’s a build break. Add a no-op binding to mark it used.🛠️ Proposed fix
- } else { - quote! { Ok(false) } - }; + } else { + quote! {{ let _ = `#params_ident`; Ok(false) }} + }; @@ - let finalize_body = quote! { Ok(()) }; + let finalize_body = quote! {{ let _ = `#params_ident`; Ok(()) }};Also applies to: 145-170
195-200: Fail fast when multiple#[light_mint]fields exist.Both paths index
light_mint_fields[0], silently ignoring additional mints. Until multi-mint support lands, emit asyn::Errorwhen more than one mint is present.🛑 Suggested guard
if total_accounts > 255 { return Err(syn::Error::new_spanned( struct_name, @@ )); } + if parsed.light_mint_fields.len() > 1 { + return Err(syn::Error::new_spanned( + struct_name, + "Multiple #[light_mint] fields are not supported yet; please use a single mint.", + )); + }Also applies to: 274-278
sdk-libs/macros/src/rentfree/program/instructions.rs (2)
44-46: Defensive check still missing onmodule.content.unwrap().While
rentfree_program_implvalidates at line 395-397, this internalcodegenfunction still uses a directunwrap(). If this function is ever called from another context, it will panic.Suggested defensive approach
- let content = module.content.as_mut().unwrap(); + let content = module.content.as_mut().ok_or_else(|| { + super::parsing::macro_error!(module, "Module must have a body") + })?;
88-91: EmptySeedParamsstruct could use a doc comment.This unit struct is generated but always empty. A brief doc comment explaining its purpose (placeholder for future seed parameters or marker type) would help readers understand why it exists.
sdk-libs/macros/src/rentfree/program/decompress.rs (2)
127-140: Hardcoded program addresses should use constants fromlight-sdk-types.These addresses are duplicated from elsewhere in the codebase. Using
LIGHT_TOKEN_PROGRAM_IDandLIGHT_TOKEN_CPI_AUTHORITYfromlight-sdk-types::constantswould centralize maintenance.
159-223: Seed derivation logic duplicatesseed_utils.rs.This function handles string literals, byte strings, and uppercase constants similarly to
seed_element_to_ref_exprinseed_utils.rs. Consider extracting a shared helper to reduce maintenance burden.
| pda::generate_rentfree_impl(&parsed) | ||
| } | ||
|
|
||
| // TODO: add a codegen file that puts the generated code together |
There was a problem hiding this comment.
Track the TODO with an issue (or remove).
Line 20 leaves a TODO in a new module; these tend to become permanent. Please add a tracking issue or implement the planned codegen aggregator.
If you want, I can draft the follow-up issue or sketch the codegen scaffold.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/mod.rs` at line 20, The TODO comment
"TODO: add a codegen file that puts the generated code together" in the rentfree
accounts module should not remain untracked; either remove the stray TODO and
implement a minimal codegen aggregator scaffold (e.g., export a generated mod
and a public function that wires generated artifacts) or replace the comment
with a reference to a tracking issue/PR number; update the module (mod.rs) to
include the scaffold's placeholder function or add the issue URL/ID in the
comment so the intended work is tracked and no orphan TODO remains.
| // Track special fields by naming convention. | ||
| // | ||
| // The RentFree derive expects these conventional field names: | ||
| // | ||
| // Fee payer (who pays transaction fees and rent): | ||
| // - "fee_payer" (preferred), "payer", "creator" | ||
| // | ||
| // Compression config (holds compression settings for the program): | ||
| // - "compression_config" | ||
| // | ||
| // CToken fields (for compressed token mint operations): | ||
| // - Config: "ctoken_compressible_config", "ctoken_config", "light_token_config_account" | ||
| // - Rent sponsor: "ctoken_rent_sponsor", "light_token_rent_sponsor" | ||
| // - Program: "ctoken_program", "light_token_program" | ||
| // - CPI authority: "ctoken_cpi_authority", "light_token_program_cpi_authority", | ||
| // "compress_token_program_cpi_authority" | ||
| // | ||
| // Fields not matching these names will use defaults in code generation. | ||
| match field_name.as_str() { | ||
| "fee_payer" | "payer" | "creator" => { | ||
| fee_payer_field = Some(field_ident.clone()); | ||
| } | ||
| "compression_config" => { | ||
| compression_config_field = Some(field_ident.clone()); | ||
| } | ||
| "ctoken_compressible_config" | "ctoken_config" | "light_token_config_account" => { | ||
| ctoken_config_field = Some(field_ident.clone()); | ||
| } | ||
| "ctoken_rent_sponsor" | "light_token_rent_sponsor" => { | ||
| ctoken_rent_sponsor_field = Some(field_ident.clone()); | ||
| } | ||
| "ctoken_program" | "light_token_program" => { | ||
| ctoken_program_field = Some(field_ident.clone()); | ||
| } | ||
| "ctoken_cpi_authority" | ||
| | "light_token_program_cpi_authority" | ||
| | "compress_token_program_cpi_authority" => { | ||
| ctoken_cpi_authority_field = Some(field_ident.clone()); | ||
| } | ||
| _ => {} | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
# First, check the full context of the parse.rs file around the mentioned lines
wc -l sdk-libs/macros/src/rentfree/accounts/parse.rsRepository: Lightprotocol/light-protocol
Length of output: 122
🏁 Script executed:
# Read the specific section and surrounding context
cat -n sdk-libs/macros/src/rentfree/accounts/parse.rs | sed -n '130,210p'Repository: Lightprotocol/light-protocol
Length of output: 4188
🏁 Script executed:
# Check if there's any warning/error handling for missing fee_payer field
rg -n "fee_payer|payer" sdk-libs/macros/src/rentfree/ -A3 -B3Repository: Lightprotocol/light-protocol
Length of output: 23796
🏁 Script executed:
# Check if custom attributes are already defined in this macro
rg -n "#\[.*payer\]|#\[fee_payer\]" sdk-libs/macros/src/ -B2 -A2Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at how the RentFree macro is defined and used
rg -n "RentFree" sdk-libs/macros/src/ -l | head -10Repository: Lightprotocol/light-protocol
Length of output: 523
🏁 Script executed:
# Check if there are any tests or examples showing how fee_payer should be used
fd -e rs -path "*/tests/*" -o -path "*/examples/*" | xargs rg -l "RentFree" | head -5Repository: Lightprotocol/light-protocol
Length of output: 520
🏁 Script executed:
# Find the resolve_field_name function definition
rg -n "fn resolve_field_name" sdk-libs/macros/src/rentfree/ -A5Repository: Lightprotocol/light-protocol
Length of output: 588
🏁 Script executed:
# Check how fee_payer_field is used when it's None
rg -n "fee_payer_field" sdk-libs/macros/src/rentfree/accounts/pda.rs -B2 -A2Repository: Lightprotocol/light-protocol
Length of output: 322
🏁 Script executed:
# Look for any validation or error checking
rg -n "must|required|error|expect|unwrap" sdk-libs/macros/src/rentfree/accounts/pda.rs | head -20Repository: Lightprotocol/light-protocol
Length of output: 474
🏁 Script executed:
# Check the full context of how parsed.fee_payer_field is handled
sed -n '95,110p' sdk-libs/macros/src/rentfree/accounts/pda.rsRepository: Lightprotocol/light-protocol
Length of output: 875
🏁 Script executed:
# Look for any tests that show how the RentFree macro behaves with missing fee_payer
rg -n "fee_payer|RentFree" sdk-libs/macros/src/rentfree/ --type-list | grep -i testRepository: Lightprotocol/light-protocol
Length of output: 461
🏁 Script executed:
# Check if the resolve_field_name is in pda.rs itself
rg -n "resolve_field_name" sdk-libs/macros/src/rentfree/accounts/ -B2 -A5Repository: Lightprotocol/light-protocol
Length of output: 2612
🏁 Script executed:
# Check if the macro generates any compile_error! for missing fee_payer
rg -n "compile_error|const_panic|error!" sdk-libs/macros/src/rentfree/accounts/ | grep -i payerRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# See how the generated code references fee_payer
sed -n '210,250p' sdk-libs/macros/src/rentfree/accounts/pda.rsRepository: Lightprotocol/light-protocol
Length of output: 1759
🏁 Script executed:
# Check if there are actual tests showing the behavior when fee_payer is missing
fd -name "*.rs" sdk-libs/macros/ -exec rg -l "test" {} \; | head -5Repository: Lightprotocol/light-protocol
Length of output: 302
🏁 Script executed:
# Look for documentation on the RentFree macro requirements
cat sdk-libs/macros/src/rentfree/README.md | head -100Repository: Lightprotocol/light-protocol
Length of output: 2660
🏁 Script executed:
# Check how the macro handles actual field reference generation
sed -n '200,270p' sdk-libs/macros/src/rentfree/accounts/pda.rsRepository: Lightprotocol/light-protocol
Length of output: 2743
🏁 Script executed:
# Check if there are any examples or tests showing intended usage
fd "test" sdk-libs/macros/src/rentfree/ -type fRepository: Lightprotocol/light-protocol
Length of output: 242
🏁 Script executed:
# Look for actual struct examples using RentFree derive
rg -n "derive.*RentFree" sdk-libs/ -B3 -A8 | head -40Repository: Lightprotocol/light-protocol
Length of output: 2649
🏁 Script executed:
# Check if the macro documentation or examples show field naming requirements
cat sdk-libs/macros/src/rentfree/accounts/mod.rs | head -60Repository: Lightprotocol/light-protocol
Length of output: 753
🏁 Script executed:
# Check if there are actual program examples that use RentFree
find . -name "*.rs" -type f -exec grep -l "derive.*RentFree" {} \; | head -10Repository: Lightprotocol/light-protocol
Length of output: 413
🏁 Script executed:
# Look at what happens in generated code when fee_payer field doesn't exist
rg -n "self\\.#fee_payer" sdk-libs/macros/src/rentfree/ -B2 -A2Repository: Lightprotocol/light-protocol
Length of output: 2662
🏁 Script executed:
# Check if there's any compile error message generation for missing fields
rg -n "did not expect|expected|error|panic|unreachable" sdk-libs/macros/src/rentfree/accounts/parse.rs | head -20Repository: Lightprotocol/light-protocol
Length of output: 331
🏁 Script executed:
# Check the learnings to understand the philosophy around field naming and attributes
cat <<'EOF'
Review the learnings about account naming and attribute usage for similar patterns
EOFRepository: Lightprotocol/light-protocol
Length of output: 154
🏁 Script executed:
# Check if custom attributes are already supported in the macro
rg -n "#\[rentfree\|#\[light_mint" sdk-libs/macros/src/rentfree/accounts/parse.rs -A3 | head -30Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Look at an actual test usage to see what field names are used
cat sdk-tests/csdk-anchor-full-derived-test/src/instruction_accounts.rs | grep -A20 "derive.*RentFree"Repository: Lightprotocol/light-protocol
Length of output: 586
🏁 Script executed:
# Check if there's validation that fee_payer_field must be Some
rg -n "fee_payer_field.*is_none\|fee_payer_field.*is_some\|require\|must" sdk-libs/macros/src/rentfree/Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check if there's any validation that produces helpful error messages for missing fee_payer
rg -n "Error::new_spanned\|syn::Error" sdk-libs/macros/src/rentfree/accounts/parse.rs | grep -v "expected named"Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# See the full context of how the generated code is used - will it definitely fail at compile time?
sed -n '95,145p' sdk-libs/macros/src/rentfree/accounts/pda.rsRepository: Lightprotocol/light-protocol
Length of output: 2120
Consider explicit #[fee_payer] attribute instead of implicit naming convention.
The macro detects the fee payer field by naming convention (lines 147-187), accepting fee_payer, payer, or creator. While documented, users who choose different names encounter generic compile-time errors rather than helpful guidance. Adding a custom attribute like #[fee_payer] would improve clarity and align with the explicit account marking patterns used elsewhere in the codebase. This is an optional refinement that would enhance developer experience.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/parse.rs` around lines 147 - 187, The
macro currently infers the fee payer account by name in parse.rs (match on
field_name, setting fee_payer_field) which leads to poor errors for differently
named fields; update the field parsing logic in the same file to first scan each
field's attributes for an explicit #[fee_payer] marker (when found set
fee_payer_field = Some(field_ident.clone()) and prefer it over the name-based
match), keep the existing name-based fallback for backward compatibility, and
adjust any related diagnostic/error messaging to suggest using #[fee_payer] when
no fee payer is found; ensure unique symbols referenced are the field attribute
parser, fee_payer_field, field_ident and the name-based match block.
| // TODO(diff-pr): Add parsing for #[rentfree_token(...)] attribute for token accounts and ATAs. | ||
| // Would need RentFreeTokenField struct with: field_ident, authority_seeds, mint field ref. | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
TODO comment for #[rentfree_token] attribute.
This indicates planned work for token account support. Based on learnings, the decompress handler should support both PDA and ctoken accounts. Consider tracking this in an issue.
Would you like me to help draft the RentFreeTokenField struct and parsing logic for #[rentfree_token(...)]? I can open an issue to track this work.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/accounts/parse.rs` around lines 244 - 246, Add a
new RentFreeTokenField AST and parser for the #[rentfree_token(...)] attribute:
define RentFreeTokenField with field_ident, authority_seeds and mint_field_ref,
add parsing logic in parse.rs alongside existing rentfree field parsers (look
for the current RentFreeField/RentFreeAccount parsing functions) to recognize
the attribute, extract the authority_seeds list and the mint field reference,
and populate the new struct; also extend the decompression handler to accept
both PDA and ctoken account types by emitting the appropriate enum/variant for
token PDAs vs ctoken accounts so downstream code can handle both cases (create a
parse_rentfree_token or extend the existing attribute parsing switch to call the
new parser and register RentFreeTokenField).
| if let Some((_, items)) = &item_mod.content { | ||
| // Inline module: mod foo { ... } | ||
| let inline_module = ParsedModule { | ||
| name: mod_name.clone(), | ||
| file: root.to_path_buf(), | ||
| path: child_path.clone(), | ||
| items: items.clone(), | ||
| }; | ||
| modules.insert(child_path, inline_module); |
There was a problem hiding this comment.
Inline modules aren’t recursed, nested structs will be missed.
Line 131-139 inserts inline modules but does not traverse their nested mod items. As a result, structs() won’t discover structs inside nested inline modules. This can silently drop #[derive(RentFree)] types and break codegen. Recommend recursively walking inline module items just like external modules.
🐛 Suggested fix (conceptual)
if let Some((_, items)) = &item_mod.content {
// Inline module: mod foo { ... }
let inline_module = ParsedModule {
name: mod_name.clone(),
file: root.to_path_buf(),
path: child_path.clone(),
items: items.clone(),
};
modules.insert(child_path.clone(), inline_module);
+ // Recurse into inline module contents
+ let child_modules = Self::parse_inline(items, root, &child_path)?;
+ modules.extend(child_modules);
} else {You’ll need a small helper (parse_inline) that mirrors parse_recursive but walks items instead of reading a file.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/crate_context.rs` around lines 131 -
139, The inline module handling in the block that creates a ParsedModule (using
ParsedModule, inserting into modules via modules.insert) does not recurse into
nested inline `mod` items, so structs() will miss types inside nested inline
modules; add a helper (e.g., parse_inline) that mirrors parse_recursive but
iterates the provided `items` AST, recursively processes any nested `Item::Mod`
inline content, and registers each nested ParsedModule into the same modules map
(or call the new helper from where inline modules are constructed) so nested
structs are discovered the same way as external modules.
| #[inline(never)] | ||
| pub fn generate_decompress_accounts_struct(variant: InstructionVariant) -> Result<syn::ItemStruct> { | ||
| // Only Mixed variant is supported - PdaOnly and TokenOnly are not implemented | ||
| match variant { | ||
| InstructionVariant::PdaOnly | InstructionVariant::TokenOnly => { | ||
| unreachable!("decompress_accounts_struct only supports Mixed variant") | ||
| } | ||
| InstructionVariant::Mixed => {} | ||
| } |
There was a problem hiding this comment.
unreachable!() for unsupported variants may cause confusing panics.
If the macro is called with PdaOnly or TokenOnly variants, this will panic at macro expansion time with an unclear message. Consider returning a proper error instead.
Suggested improvement
match variant {
InstructionVariant::PdaOnly | InstructionVariant::TokenOnly => {
- unreachable!("decompress_accounts_struct only supports Mixed variant")
+ return Err(syn::Error::new(
+ proc_macro2::Span::call_site(),
+ "decompress_accounts_struct currently only supports Mixed variant (both PDA and token seeds)"
+ ));
}
InstructionVariant::Mixed => {}
}🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/decompress.rs` around lines 98 - 106,
The match in generate_decompress_accounts_struct currently uses unreachable!()
for InstructionVariant::PdaOnly and ::TokenOnly which can panic at macro
expansion time; change this to return an Err with a descriptive syn::Error (or
the crate's preferred Error type) instead of panicking. Locate
generate_decompress_accounts_struct and replace the unreachable! branch for
PdaOnly and TokenOnly with a return Err(...) that includes a clear message like
"decompress_accounts_struct only supports Mixed variant" and uses
syn::Error::new_spanned or equivalent so the caller receives a proper
compile-time error rather than a panic.
| Expr::Call(call_expr) => { | ||
| let mut new_call = call_expr.clone(); | ||
| new_call.args = call_expr | ||
| .args | ||
| .iter() | ||
| .map(|a| transform_expr_for_ctx_seeds(a, ctx_field_names)) | ||
| .collect(); | ||
| Expr::Call(new_call) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n sdk-libs/macros/src/rentfree/program/expr_traversal.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 200
🏁 Script executed:
# Get file size to understand if we should read entirely
wc -l sdk-libs/macros/src/rentfree/program/expr_traversal.rsRepository: Lightprotocol/light-protocol
Length of output: 129
🏁 Script executed:
# Look for the function definition and surrounding context
sed -n '1,150p' sdk-libs/macros/src/rentfree/program/expr_traversal.rs | cat -nRepository: Lightprotocol/light-protocol
Length of output: 3672
🏁 Script executed:
# Search for test files related to this module
fd -t f ".*test.*" sdk-libs/macros/src/ | grep -E "(test|spec)"Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check if there are tests in the same file or related test modules
grep -r "transform_expr_for_ctx_seeds" sdk-libs/macros/src/ -A 5 -B 5Repository: Lightprotocol/light-protocol
Length of output: 6018
Transform the call callee too, not just its args.
Lines 63-70 rewrite Expr::Call arguments but leave func untouched. If a seed expression calls a function on a field—like data.seed_fn() or ctx.accounts.get_seed() —the transformation won't apply to the callee itself, leaving broken references in the generated code. The handler should recursively transform call_expr.func just as it does with Expr::MethodCall's receiver.
🐛 Suggested fix
Expr::Call(call_expr) => {
let mut new_call = call_expr.clone();
+ new_call.func = Box::new(transform_expr_for_ctx_seeds(
+ &call_expr.func,
+ ctx_field_names,
+ ));
new_call.args = call_expr
.args
.iter()Consider adding a test that verifies transformation of a call expression where the callee is data.field or ctx.accounts.field.
📝 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.
| Expr::Call(call_expr) => { | |
| let mut new_call = call_expr.clone(); | |
| new_call.args = call_expr | |
| .args | |
| .iter() | |
| .map(|a| transform_expr_for_ctx_seeds(a, ctx_field_names)) | |
| .collect(); | |
| Expr::Call(new_call) | |
| Expr::Call(call_expr) => { | |
| let mut new_call = call_expr.clone(); | |
| new_call.func = Box::new(transform_expr_for_ctx_seeds( | |
| &call_expr.func, | |
| ctx_field_names, | |
| )); | |
| new_call.args = call_expr | |
| .args | |
| .iter() | |
| .map(|a| transform_expr_for_ctx_seeds(a, ctx_field_names)) | |
| .collect(); | |
| Expr::Call(new_call) |
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/expr_traversal.rs` around lines 63 - 70,
The Expr::Call arm currently transforms only call_expr.args but leaves
call_expr.func unchanged, so update the Expr::Call handler in
transform_expr_for_ctx_seeds to recursively transform the callee as well: apply
transform_expr_for_ctx_seeds to call_expr.func (similar to how the
Expr::MethodCall receiver is handled), assign the transformed func back into
new_call.func, and then transform args as already implemented; also add a unit
test covering a call whose callee is a field access like data.seed_fn() or
ctx.accounts.get_seed() to ensure the callee transformation is applied.
| pub fn is_ctx_accounts(base: &Expr) -> bool { | ||
| if let Expr::Field(nested) = base { | ||
| if let Member::Named(member) = &nested.member { | ||
| return member == "accounts" && Self::is_path_ident(&nested.base, "ctx"); | ||
| } | ||
| } | ||
| false | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider making is_ctx_accounts private.
This helper method is marked pub but appears to only be used within this module (by classify_field_expr and map_call_arg). Making it pub(crate) or private would better encapsulate the implementation.
- pub fn is_ctx_accounts(base: &Expr) -> bool {
+ fn is_ctx_accounts(base: &Expr) -> bool {📝 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.
| pub fn is_ctx_accounts(base: &Expr) -> bool { | |
| if let Expr::Field(nested) = base { | |
| if let Member::Named(member) = &nested.member { | |
| return member == "accounts" && Self::is_path_ident(&nested.base, "ctx"); | |
| } | |
| } | |
| false | |
| } | |
| fn is_ctx_accounts(base: &Expr) -> bool { | |
| if let Expr::Field(nested) = base { | |
| if let Member::Named(member) = &nested.member { | |
| return member == "accounts" && Self::is_path_ident(&nested.base, "ctx"); | |
| } | |
| } | |
| false | |
| } |
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/program/visitors.rs` around lines 92 - 99, The
helper function is_ctx_accounts is currently public but only used internally by
classify_field_expr and map_call_arg; change its visibility to restrict external
access (make it private or pub(crate)) by updating the signature of
is_ctx_accounts to remove the pub modifier (or use pub(crate) if module-wide
access is needed), keeping its implementation and references in
classify_field_expr and map_call_arg unchanged so callers still resolve; ensure
no external code relies on is_ctx_accounts before finalizing the visibility
change.
| //! Shared trait derive macros for compressible accounts. | ||
| //! | ||
| //! This module provides: | ||
| //! - `seed_extraction` - Seed extraction from Anchor account attributes | ||
| //! - `decompress_context` - Decompression context utilities | ||
| //! - `light_compressible` - Combined RentFreeAccount derive macro | ||
| //! - `pack_unpack` - Pack/Unpack trait implementations | ||
| //! - `traits` - HasCompressionInfo, Compressible, CompressAs traits | ||
| //! - `utils` - Shared utility functions |
There was a problem hiding this comment.
Update module docs to match rentfree terminology.
Line 1-9 still says “compressible accounts,” which is now misleading in the rentfree refactor and can cause confusion in public docs. Recommend updating the wording to “rentfree accounts” (or explicitly note compatibility).
✏️ Suggested edit
-//! Shared trait derive macros for compressible accounts.
+//! Shared trait derive macros for rentfree accounts.🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/traits/mod.rs` around lines 1 - 9, Update the
module-level documentation comment at the top of the file to use "rentfree
accounts" instead of "compressible accounts" and/or add an explicit note about
compatibility with legacy compressible terminology; specifically edit the module
docstring that lists `seed_extraction`, `decompress_context`,
`light_compressible`, `pack_unpack`, `traits`, and `utils` so the first line and
description reflect "rentfree accounts" (or state that these macros remain
compatible with compressible accounts) to avoid confusion in public docs.
| impl FromMeta for CompressAsFields { | ||
| fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> { | ||
| items | ||
| .iter() | ||
| .map(|item| match item { | ||
| darling::ast::NestedMeta::Meta(syn::Meta::NameValue(nv)) => { | ||
| let name = nv.path.get_ident().cloned().ok_or_else(|| { | ||
| darling::Error::custom("expected field identifier").with_span(&nv.path) | ||
| })?; | ||
| Ok(CompressAsField { | ||
| name, | ||
| value: nv.value.clone(), | ||
| }) | ||
| } | ||
| other => Err(darling::Error::custom("expected field = expr").with_span(other)), | ||
| }) | ||
| .collect::<darling::Result<Vec<_>>>() | ||
| .map(|fields| CompressAsFields { fields }) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
# First, locate the file and examine its structure
fd -p "traits\.rs" | grep -E "(rentfree|traits)"Repository: Lightprotocol/light-protocol
Length of output: 200
🏁 Script executed:
# Search for #[compress_as] usages in the codebase
rg -n "compress_as" --type rs -B2 -A2Repository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Check if there are any marker-style usages (without parentheses)
rg -nP '#\[\s*compress_as\s*\]' --type rsRepository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Look at the actual implementation file
fd "traits.rs" | xargs -I {} sh -c 'echo "=== {} ===" && wc -l {} && head -60 {}'Repository: Lightprotocol/light-protocol
Length of output: 8529
🏁 Script executed:
# Search more broadly for #[compress_as] usages
rg "compress_as" -n -B3 -A3Repository: Lightprotocol/light-protocol
Length of output: 15809
🏁 Script executed:
# Check the full file to understand context
cat -n sdk-libs/macros/src/rentfree/traits/traits.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 4245
🏁 Script executed:
# Look for where FromMeta is used in derive macro generation
rg "FromMeta\|CompressAsFields" -n -B2 -A5Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check if there are any tests or examples using #[compress_as]
fd "\.(rs|md)$" | xargs rg "#\[compress_as" -nRepository: Lightprotocol/light-protocol
Length of output: 790
FromMeta implementation is intentional; marker-only #[compress_as] unsupported by design.
The implementation only supports #[compress_as(field = value, ...)] syntax and does not provide from_word() for marker-only #[compress_as]. This is intentional—the attribute requires field overrides or should be omitted entirely.
No existing code uses marker-only syntax (all six codebase usages provide arguments). The attribute is properly optional at the top level, so users who don't need field overrides simply omit it rather than use a marker. If you want marker-only support for users who prefer explicit syntax over implicit omission, add from_word() and document the behavior; otherwise, consider documenting in the macro docs that the attribute requires arguments or omission.
🤖 Prompt for AI Agents
In `@sdk-libs/macros/src/rentfree/traits/traits.rs` around lines 25 - 43, The
current FromMeta impl for CompressAsFields only implements from_list and rejects
marker-only attributes; to support marker-only `#[compress_as]` add a
from_word() implementation that returns Ok(CompressAsFields { fields: Vec::new()
}) so the parser accepts the bare attribute, keeping the existing from_list
logic in place; implement this as a FromMeta::from_word method on
CompressAsFields so marker-only usage is parsed while existing field overrides
via from_list still work.
Summary by CodeRabbit
Refactor
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.