Skip to content

generate_idl! macro does not include #[account_type] types defined in path-dependency crates #176

@s-tikhomirov

Description

@s-tikhomirov

I've been working on integration of lez-payment-streams into logos-delivery. I looked at whisper-wall as inspiration. Turns out it is non-trivial to pull (and parse) account data from LEZ. Whisper-wall uses C FFI for the whisper_wall SPEL program. This approach pre-dates this SPEL commit #154 - included into v0.2.0-rc.5 - that should simplify the process, but apparently the structure of my payment streams project doesn't fit into what IDL generation expects, and prevents me from taking advantage of that recent SPEL feature.

AI offered an explanation and a fix, but I feel it'd be impolite to push a fully AI-written PR here with very limited understanding on my part of what is going on. I'm not sure I have resources to establish such understanding, so I'm leaving the following AI-written breakdown hoping it could be helpful to people (or agents, for that matter) working closely with SPEL.


When account types annotated with #[account_type] are defined in a separate crate referenced via a local path = "..." dependency, the generated IDL's top-level accounts array is always empty. Only types defined directly in the file passed to generate_idl! are discovered.

Reproducer

A typical project structure:

my_program/
  methods/guest/Cargo.toml        # depends on my_program_core via path = "../../my_program_core"
  methods/guest/src/bin/program.rs  # #[lez_program], no #[account_type] here
  my_program_core/src/lib.rs      # #[account_type] on VaultConfig, StreamConfig, ...
  examples/src/bin/generate_idl.rs  # spel_framework::generate_idl!("../methods/guest/src/bin/program.rs")

Running cargo run --bin generate_idl produces an IDL with "accounts": [] even though my_program_core contains multiple #[account_type]-annotated types.

This is the standard structure for any non-trivial program that separates on-chain types into a shared core crate (for reuse in tests, clients, etc.).

Root causes

Two independent issues combine to cause the empty output.

Issue 1 — path-dep crates are not scanned.
expand_generate_idl in spel-framework-macros parses only the single .rs file passed to it. It never follows the crate's Cargo.toml to find local path dependencies. By contrast, spel-cli generate-idl already handles this correctly: it calls find_path_dep_dirs to collect local path entries from Cargo.toml, then passes them to generate_idl_from_file_with_deps, which recursively parses src/lib.rs of each dep and follows mod declarations.

The proc macro needs the same treatment. The CARGO_MANIFEST_DIR environment variable and the source file path are both available at macro expansion time, so the nearest Cargo.toml can be found by walking up from the source file.

Issue 2 — qualified attribute form not recognised.
has_account_type_attr in spel-framework-core/src/account_types.rs checks a.path().is_ident("account_type"). This matches only the bare form #[account_type]. When users write #[spel_framework_macros::account_type] (the fully-qualified path, which is idiomatic when importing via a path rather than a use declaration), the check returns false and the type is silently skipped. The fix is to also match any attribute path whose last segment is account_type.

Proposed fix

  1. In spel-framework-core/src/account_types.rs, update has_account_type_attr to recognise both forms:

    pub(crate) fn has_account_type_attr(attrs: &[Attribute]) -> bool {
        attrs.iter().any(|a| {
            let path = a.path();
            path.is_ident("account_type")
                || path.segments.last().map_or(false, |s| s.ident == "account_type")
        })
    }
  2. Expose collect_items_from_crate_dirs as pub in spel-framework-core/src/idl_gen.rs so the proc macro can reuse the existing recursive mod-following logic.

  3. Add toml as a dependency of spel-framework-macros.

  4. In spel-framework-macros/src/lib.rs, add helpers that mirror the spel-cli logic:

    • find_crate_manifest_macro(path) — walks up from the source file to the nearest Cargo.toml
    • find_path_dep_dirs_macro(path) — parses [dependencies] for path = "..." entries (excluding dev/build deps)
  5. In expand_generate_idl, call these helpers and merge the collected items into all_items before calling collect_account_types.

Notes

  • The fix has no effect on programs that define all account types in the guest file itself or use the bare #[account_type] form — behaviour is unchanged in those cases.
  • The #[lez_program] macro path (expand_lez_program) has the same gap for the qualified attribute form, since it also calls collect_account_types. The attribute fix resolves it there too.
  • spel-cli generate-idl already produces the correct output because it uses generate_idl_from_file_with_deps. The gap is specific to the generate_idl! proc macro.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions