Skip to content

(do not merge) comptime fn name guarantees for napi bindings#198

Closed
spiral-ladder wants to merge 36 commits intonapifrom
bing/comptime-fn-name-bindings
Closed

(do not merge) comptime fn name guarantees for napi bindings#198
spiral-ladder wants to merge 36 commits intonapifrom
bing/comptime-fn-name-bindings

Conversation

@spiral-ladder
Copy link
Copy Markdown
Collaborator

@spiral-ladder spiral-ladder commented Jan 28, 2026

Introduces makeNapiPropertyDescriptor that guarantees, at compile-time, that a given function exists within our bindings.

built on top of @guha-rahul 's PR #189

See relevant commits for a quick demo
f119052
13fedac

@spiral-ladder spiral-ladder self-assigned this Jan 28, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @spiral-ladder, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust compile-time mechanism for NAPI bindings in Zig, ensuring that exposed JavaScript properties correctly map to underlying Zig functions. Beyond this core improvement, it substantially broadens the NAPI interface for BeaconStateView, making a wider array of Beacon Chain state data and state transition utilities accessible. This expansion is supported by internal refactorings, including a more detailed voluntary exit validation process, enhanced epoch caching with decision roots, and new utility functions for state analysis. The changes aim to improve the reliability and functionality of the NAPI bindings, providing more comprehensive and type-safe access to the Beacon Chain state.

Highlights

  • Compile-time NAPI Binding Guarantees: Introduced makeNapiPropertyDescriptor in BeaconStateView.zig to provide compile-time verification that NAPI property names correspond to existing Zig functions, enhancing type safety and preventing runtime errors.
  • Expanded BeaconStateView NAPI Surface: Significantly extended the BeaconStateView NAPI bindings with numerous new methods and getters, exposing more state transition logic and data from the BeaconState to the JavaScript/TypeScript environment. This includes methods for validator status, block roots, historical summaries, pending deposits/withdrawals, proposer lookahead, and Merkle proofs.
  • Refactored Voluntary Exit Logic: The isValidVoluntaryExit function was refactored into getVoluntaryExitValidity to return a detailed VoluntaryExitValidity enum, providing specific reasons for an invalid voluntary exit instead of a simple boolean.
  • Epoch Cache Enhancements: The EpochCache now stores previous_decision_root, current_decision_root, and next_decision_root to facilitate shuffling cache lookups, and includes new utility functions for calculating these roots.
  • TypeScript Type Definitions Update: The bindings/src/index.ts file was updated with new interfaces and type definitions to reflect the expanded NAPI functionality, ensuring type safety for consumers of the bindings.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Comment on lines +734 to +735
makeNapiPropertyDescriptor(0, "slot"),
.{ .utf8name = "slott", .getter = napi.wrapCallback(0, BeaconStateView_slot) },
Copy link
Copy Markdown
Collaborator Author

@spiral-ladder spiral-ladder Jan 28, 2026

Choose a reason for hiding this comment

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

this is the key benefit, if we misspell slott in line 735 this compiles fine and we'd probably only catch this at runtime. with a 2-line helper fn we use at 734 we can guarantee at compile time this doesn't happen.

if we do makeNapiPropertyDescriptor(0, "slot") we run into:

zig build build-lib:bindings
build-lib:bindings
└─ install bindings
   └─ zig build-lib bindings Debug native 1 errors
bindings/napi/BeaconStateView.zig:724:75: error: root source file struct 'BeaconStateView' has no member named 'BeaconStateView_slott'
    return .{ .utf8name = name.ptr, .getter = napi.wrapCallback(argc_cap, @field(@This(), func_name)) };
                                                                          ^~~~~~~~~~~~~~~~~~~~~~~~~~

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a makeNapiPropertyDescriptor helper function to provide compile-time guarantees for N-API bindings, which is a valuable improvement for ensuring the correctness of the bindings. The changes also include a significant amount of refactoring and the addition of many new features to the BeaconStateView bindings. My review includes suggestions to improve the new helper function, fix a typo, and align with the repository's coding style guide.

Comment thread bindings/napi/BeaconStateView.zig
Comment on lines +718 to +725
fn makeNapiPropertyDescriptor(
comptime argc_cap: usize,
comptime name: []const u8,
) napi.c.napi_property_descriptor {
const func_name = @typeName(BeaconStateView) ++ "_" ++ name;

return .{ .utf8name = name.ptr, .getter = napi.wrapCallback(argc_cap, @field(@This(), func_name)) };
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This helper function is a great idea for compile-time safety. It could be extended to support methods as well, which would allow it to be used for more of the properties in the register function.

Here's a suggested implementation that supports both getters and methods:

fn makeNapiPropertyDescriptor(
    comptime prop_type: enum { getter, method },
    comptime argc_cap: usize,
    comptime name: []const u8,
) napi.c.napi_property_descriptor {
    const func_name = @typeName(BeaconStateView) ++ "_" ++ name;
    const func = @field(@This(), func_name);
    const callback = napi.wrapCallback(argc_cap, func);

    return switch (prop_type) {
        .getter => .{ .utf8name = name.ptr, .getter = callback },
        .method => .{ .utf8name = name.ptr, .method = callback },
    };
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

yes we can extend this helper fn

Comment thread bindings/napi/root.zig
const shuffle = @import("./shuffle.zig");
const proposer_index = @import("./proposer_index.zig");
const beaconStateView = @import("./beacon_state_view.zig");
const BeaconStateView = @import("./BeaconStateView.zig");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The filename BeaconStateView.zig does not follow the snake_case convention for filenames specified in the style guide. Please rename it to beacon_state_view.zig. The imported module variable should also be camelCase (beaconStateView) as per the style guide. This will require updating its usage on line 31 as well.

const beaconStateView = @import("./beacon_state_view.zig");
References
  1. Style guide line 212 states: "Use snake_case for file names. ... We don't use Zig's CamelCase.zig style for 'struct' files to keep the convention simple and consistent." It also states to use camelCase for variable names. (link)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

let's not follow tigerstyle for this rule

Comment on lines 39 to +51
var validators = try state.validators();
const validators_len = try validators.length();
if (voluntary_exit.validator_index >= validators_len) {
return false;
return .inactive;
}

var validator = try validators.get(@intCast(voluntary_exit.validator_index));
const current_epoch = epoch_cache.epoch;

const activation_epoch = try validator.get("activation_epoch");
// verify the validator is active
if (!try isActiveValidatorView(&validator, current_epoch)) {
return .inactive;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The logic for checking validator active status is duplicated. The check for voluntary_exit.validator_index >= validators_len on line 41 is effectively checking if the validator exists. If it doesn't, isActiveValidatorView will also return false (as it will be checking a default/zeroed validator struct if validators.get returns one for an out-of-bounds index, which won't be active). You can simplify this by removing the initial check.

comptime argc_cap: usize,
comptime name: []const u8,
) napi.c.napi_property_descriptor {
const func_name = @typeName(BeaconStateView) ++ "_" ++ name;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

if we omit the BeaconStateView_ prefix (since we converted this into a top-level struct) we can just do @field(@This(), name) directly which is even nicer. cc @wemeetagain we spoke about naming with the prefix in our review call

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants