From ef1a3481734feb733c072b385401d678fe99dafa Mon Sep 17 00:00:00 2001 From: bing Date: Fri, 22 May 2026 15:55:27 +0800 Subject: [PATCH 1/2] feat: warn when dsl incompatible functions are skipped Zig functions are skipped if they: 1) are not `pub fn`, 2) do not use DSL-compatible types from the `js` module We can't error since we want flexibility to define and use other functions in the same file, so we just add a simple warning to warn the user about this at comptime. This is to avoid future situations encountered here: https://github.com/ChainSafe/lodestar-z/pull/371 --- build.zig.zon | 15 +++++++++++++ examples/non_dsl_warning/mod.test.ts | 32 ++++++++++++++++++++++++++++ examples/non_dsl_warning/mod.zig | 29 +++++++++++++++++++++++++ src/js/export_module.zig | 5 ++++- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 examples/non_dsl_warning/mod.test.ts create mode 100644 examples/non_dsl_warning/mod.zig diff --git a/build.zig.zon b/build.zig.zon index b29943f..cb03bea 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -49,6 +49,11 @@ .imports = .{.zapi}, .link_libc = true, }, + .example_non_dsl_warning = .{ + .root_source_file = "examples/non_dsl_warning/mod.zig", + .imports = .{.zapi}, + .link_libc = true, + }, }, .libraries = .{ .example_hello_world = .{ @@ -69,6 +74,12 @@ .linker_allow_shlib_undefined = true, .dest_sub_path = "example_js_dsl.node", }, + .example_non_dsl_warning = .{ + .root_module = .example_non_dsl_warning, + .linkage = .dynamic, + .linker_allow_shlib_undefined = true, + .dest_sub_path = "example_non_dsl_warning.node", + }, }, .tests = .{ .napi = .{ .root_module = .napi }, @@ -88,5 +99,9 @@ .root_module = .example_js_dsl, .linker_allow_shlib_undefined = true, }, + .example_non_dsl_warning = .{ + .root_module = .example_non_dsl_warning, + .linker_allow_shlib_undefined = true, + }, }, } diff --git a/examples/non_dsl_warning/mod.test.ts b/examples/non_dsl_warning/mod.test.ts new file mode 100644 index 0000000..c36ce15 --- /dev/null +++ b/examples/non_dsl_warning/mod.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; +import { spawnSync } from "node:child_process"; +const non_dsl_mod = require("../../zig-out/lib/example_non_dsl_warning.node"); + +const cwd = new URL("../..", import.meta.url); + +function runNode(source: string) { + return spawnSync(process.execPath, ["-e", source], { + cwd, + encoding: "utf8", + }); +} + +describe("non-DSL fn should warn user", () => { + it("exports DSL functions and skips non-DSL functions", () => { + const result = runNode(` + const mod = require("./zig-out/lib/example_non_dsl_warning.node"); + process.stdout.write(JSON.stringify({ + exported: mod.exported(41), + skipped: typeof mod.skipped, + })); + `); + + expect(result.status).toEqual(0); + expect(JSON.parse(result.stdout)).toEqual({ + exported: 42, + skipped: "undefined", + }); + + expect(result.stderr).toContain("zapi: skipping non-DSL function mod.skipped, this will not be exported"); + }); +}); diff --git a/examples/non_dsl_warning/mod.zig b/examples/non_dsl_warning/mod.zig new file mode 100644 index 0000000..966341e --- /dev/null +++ b/examples/non_dsl_warning/mod.zig @@ -0,0 +1,29 @@ +//! This example module demonstrates a public function with non-DSL parameters +//! being skipped by `zapi`'s `exportModule` functionality. +//! +//! `zapi` will only export zig functions as bindings if they: +//! +//! 1) are public functions (`pub fn`), +//! 2) use DSL-compatible parameters (eg. `js.Number`). +//! +//! In this scenario, even though `skipped` is public, it does not use +//! a DSL-compatible type for its `value` parameter and is therefore +//! not exported. +//! +//! If the user should want to export any other functions manually, +//! they would need to pass a custom `.register` to `js.exportModule`. +const js = @import("zapi").js; + +const Number = js.Number; + +pub fn exported(value: Number) Number { + return Number.from(value.assertI32() + 1); +} + +pub fn skipped(value: u32) u32 { + return value + 1; +} + +comptime { + js.exportModule(@This(), .{}); +} diff --git a/src/js/export_module.zig b/src/js/export_module.zig index aa70ba1..c28ad68 100644 --- a/src/js/export_module.zig +++ b/src/js/export_module.zig @@ -150,7 +150,10 @@ fn registerDecls(comptime Module: type, env: napi.Env, module: napi.Value, compt } break :blk true; }; - if (!is_dsl_fn) continue; + if (!is_dsl_fn) { + std.log.warn("zapi: skipping non-DSL function {s}.{s}, this will not be exported", .{ @typeName(Module), decl.name }); + continue; + } // DSL function — wrap and register const cb = wrap_function.wrapFunction(field); From 5b585bb32175401565e40e4783aaf7cfad2f5f17 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Tue, 26 May 2026 14:34:22 +0200 Subject: [PATCH 2/2] fix: use the compile time error --- build.zig.zon | 15 ------------- examples/non_dsl_warning/mod.test.ts | 32 ---------------------------- examples/non_dsl_warning/mod.zig | 29 ------------------------- src/js/export_module.zig | 5 +---- 4 files changed, 1 insertion(+), 80 deletions(-) delete mode 100644 examples/non_dsl_warning/mod.test.ts delete mode 100644 examples/non_dsl_warning/mod.zig diff --git a/build.zig.zon b/build.zig.zon index cb03bea..b29943f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -49,11 +49,6 @@ .imports = .{.zapi}, .link_libc = true, }, - .example_non_dsl_warning = .{ - .root_source_file = "examples/non_dsl_warning/mod.zig", - .imports = .{.zapi}, - .link_libc = true, - }, }, .libraries = .{ .example_hello_world = .{ @@ -74,12 +69,6 @@ .linker_allow_shlib_undefined = true, .dest_sub_path = "example_js_dsl.node", }, - .example_non_dsl_warning = .{ - .root_module = .example_non_dsl_warning, - .linkage = .dynamic, - .linker_allow_shlib_undefined = true, - .dest_sub_path = "example_non_dsl_warning.node", - }, }, .tests = .{ .napi = .{ .root_module = .napi }, @@ -99,9 +88,5 @@ .root_module = .example_js_dsl, .linker_allow_shlib_undefined = true, }, - .example_non_dsl_warning = .{ - .root_module = .example_non_dsl_warning, - .linker_allow_shlib_undefined = true, - }, }, } diff --git a/examples/non_dsl_warning/mod.test.ts b/examples/non_dsl_warning/mod.test.ts deleted file mode 100644 index c36ce15..0000000 --- a/examples/non_dsl_warning/mod.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { spawnSync } from "node:child_process"; -const non_dsl_mod = require("../../zig-out/lib/example_non_dsl_warning.node"); - -const cwd = new URL("../..", import.meta.url); - -function runNode(source: string) { - return spawnSync(process.execPath, ["-e", source], { - cwd, - encoding: "utf8", - }); -} - -describe("non-DSL fn should warn user", () => { - it("exports DSL functions and skips non-DSL functions", () => { - const result = runNode(` - const mod = require("./zig-out/lib/example_non_dsl_warning.node"); - process.stdout.write(JSON.stringify({ - exported: mod.exported(41), - skipped: typeof mod.skipped, - })); - `); - - expect(result.status).toEqual(0); - expect(JSON.parse(result.stdout)).toEqual({ - exported: 42, - skipped: "undefined", - }); - - expect(result.stderr).toContain("zapi: skipping non-DSL function mod.skipped, this will not be exported"); - }); -}); diff --git a/examples/non_dsl_warning/mod.zig b/examples/non_dsl_warning/mod.zig deleted file mode 100644 index 966341e..0000000 --- a/examples/non_dsl_warning/mod.zig +++ /dev/null @@ -1,29 +0,0 @@ -//! This example module demonstrates a public function with non-DSL parameters -//! being skipped by `zapi`'s `exportModule` functionality. -//! -//! `zapi` will only export zig functions as bindings if they: -//! -//! 1) are public functions (`pub fn`), -//! 2) use DSL-compatible parameters (eg. `js.Number`). -//! -//! In this scenario, even though `skipped` is public, it does not use -//! a DSL-compatible type for its `value` parameter and is therefore -//! not exported. -//! -//! If the user should want to export any other functions manually, -//! they would need to pass a custom `.register` to `js.exportModule`. -const js = @import("zapi").js; - -const Number = js.Number; - -pub fn exported(value: Number) Number { - return Number.from(value.assertI32() + 1); -} - -pub fn skipped(value: u32) u32 { - return value + 1; -} - -comptime { - js.exportModule(@This(), .{}); -} diff --git a/src/js/export_module.zig b/src/js/export_module.zig index c28ad68..73191c3 100644 --- a/src/js/export_module.zig +++ b/src/js/export_module.zig @@ -150,10 +150,7 @@ fn registerDecls(comptime Module: type, env: napi.Env, module: napi.Value, compt } break :blk true; }; - if (!is_dsl_fn) { - std.log.warn("zapi: skipping non-DSL function {s}.{s}, this will not be exported", .{ @typeName(Module), decl.name }); - continue; - } + if (!is_dsl_fn) @compileError("zapi: cannot export non-DSL `pub fn " ++ @typeName(Module) ++ "." ++ decl.name ++ "` — use DSL params (e.g. `js.Number`), drop `pub`, or pass `.register` to `exportModule` to export it manually"); // DSL function — wrap and register const cb = wrap_function.wrapFunction(field);