From c9b114ff6bca72d66d01224b86af79b276d3cd98 Mon Sep 17 00:00:00 2001 From: richerfu Date: Tue, 10 Mar 2026 09:14:22 +0800 Subject: [PATCH] Add union and enum support --- examples/basic/index.d.ts | 42 +++++++ examples/basic/src/enum.zig | 35 ++++++ examples/basic/src/hello.zig | 31 ++++++ examples/basic/src/union.zig | 174 +++++++++++++++++++++++++++++ src/build/napi-tsgen.zig | 29 ++++- src/napi/util/napi.zig | 208 ++++++++++++++++++++++++++++++++++- src/napi/value/function.zig | 17 +++ src/napi/value/number.zig | 46 ++++---- src/napi/wrapper/class.zig | 47 ++++++++ src/napi/wrapper/error.zig | 5 + 10 files changed, 608 insertions(+), 26 deletions(-) create mode 100644 examples/basic/src/enum.zig create mode 100644 examples/basic/src/union.zig diff --git a/examples/basic/index.d.ts b/examples/basic/index.d.ts index 3da4ea2..bf845b5 100644 --- a/examples/basic/index.d.ts +++ b/examples/basic/index.d.ts @@ -45,6 +45,23 @@ export declare class TestFactoryClass { format(): string } +export interface MessagePayload { + title: string + count: number +} + +export declare const enum Color { + Red = 1, + Green = 2, + Blue = 4, +} + +export declare const enum StringColor { + Red = 'Red', + Green = 'Green', + Blue = 'Blue', +} + export declare function test_i32(left: number, right: number): number export declare function test_f32(left: number, right: number): number @@ -81,3 +98,28 @@ export declare function create_dataview(): DataView export declare function get_dataview_length(view: DataView): number export declare function get_dataview_first_byte(view: DataView): number export declare function get_dataview_uint32_le(view: DataView): number +export declare function union_identity(value: number | string): number | string +export declare function make_union(is_number: boolean): number | string +export declare function union_kind(value: number | string): string +export declare function object_or_text_identity(value: MessagePayload | string): MessagePayload | string +export declare function make_object_or_text(as_object: boolean): MessagePayload | string +export declare function object_or_array_identity(value: MessagePayload | Array): MessagePayload | Array +export declare function tuple_or_text_identity(value: [number, boolean, string] | string): [number, boolean, string] | string +export declare function flip_flag_or_increment(value: boolean | number): boolean | number +export declare function color_or_text_identity(value: Color | string): Color | string +export declare function favorite_color_or_text(use_color: boolean): Color | string +export declare function maybe_text_or_count_identity(value: string | undefined | null | number): string | undefined | null | number +export declare function make_maybe_text_or_count(as_text: boolean): string | undefined | null | number +export declare function buffer_or_text_identity(value: Buffer | string): Buffer | string +export declare function make_buffer_or_text(use_buffer: boolean): Buffer | string +export declare function arraybuffer_or_array_identity(value: ArrayBuffer | Array): ArrayBuffer | Array +export declare function make_arraybuffer_or_array(use_arraybuffer: boolean): ArrayBuffer | Array +export declare function payload_or_color_identity(value: MessagePayload | Color): MessagePayload | Color +export declare function make_payload_or_color(use_payload: boolean): MessagePayload | Color +export declare function payload_or_string_color_identity(value: MessagePayload | StringColor): MessagePayload | StringColor +export declare function make_payload_or_string_color(use_payload: boolean): MessagePayload | StringColor +export declare function enum_identity(color: Color): Color +export declare function favorite_color(): Color +export declare function is_primary(color: Color): boolean +export declare function string_enum_identity(color: StringColor): StringColor +export declare function favorite_string_color(): StringColor diff --git a/examples/basic/src/enum.zig b/examples/basic/src/enum.zig new file mode 100644 index 0000000..10ba36f --- /dev/null +++ b/examples/basic/src/enum.zig @@ -0,0 +1,35 @@ +pub const Color = enum(i32) { + Red = 1, + Green = 2, + Blue = 4, +}; + +pub fn enum_identity(color: Color) Color { + return color; +} + +pub fn favorite_color() Color { + return .Green; +} + +pub fn is_primary(color: Color) bool { + return switch (color) { + .Red, .Green, .Blue => true, + }; +} + +pub const StringColor = enum { + Red, + Green, + Blue, + + pub const napi_string_enum = true; +}; + +pub fn string_enum_identity(color: StringColor) StringColor { + return color; +} + +pub fn favorite_string_color() StringColor { + return .Blue; +} diff --git a/examples/basic/src/hello.zig b/examples/basic/src/hello.zig index e278774..5eab9cc 100644 --- a/examples/basic/src/hello.zig +++ b/examples/basic/src/hello.zig @@ -15,6 +15,8 @@ const arraybuffer = @import("arraybuffer.zig"); const typedarray = @import("typedarray.zig"); const dataview = @import("dataview.zig"); const reference = @import("reference.zig"); +const union_value = @import("union.zig"); +const enum_value = @import("enum.zig"); pub const test_i32 = number.test_i32; pub const test_f32 = number.test_f32; @@ -69,6 +71,35 @@ pub const get_dataview_length = dataview.get_dataview_length; pub const get_dataview_first_byte = dataview.get_dataview_first_byte; pub const get_dataview_uint32_le = dataview.get_dataview_uint32_le; +pub const union_identity = union_value.union_identity; +pub const make_union = union_value.make_union; +pub const union_kind = union_value.union_kind; +pub const object_or_text_identity = union_value.object_or_text_identity; +pub const make_object_or_text = union_value.make_object_or_text; +pub const object_or_array_identity = union_value.object_or_array_identity; +pub const tuple_or_text_identity = union_value.tuple_or_text_identity; +pub const flip_flag_or_increment = union_value.flip_flag_or_increment; +pub const color_or_text_identity = union_value.color_or_text_identity; +pub const favorite_color_or_text = union_value.favorite_color_or_text; +pub const maybe_text_or_count_identity = union_value.maybe_text_or_count_identity; +pub const make_maybe_text_or_count = union_value.make_maybe_text_or_count; +pub const buffer_or_text_identity = union_value.buffer_or_text_identity; +pub const make_buffer_or_text = union_value.make_buffer_or_text; +pub const arraybuffer_or_array_identity = union_value.arraybuffer_or_array_identity; +pub const make_arraybuffer_or_array = union_value.make_arraybuffer_or_array; +pub const payload_or_color_identity = union_value.payload_or_color_identity; +pub const make_payload_or_color = union_value.make_payload_or_color; +pub const payload_or_string_color_identity = union_value.payload_or_string_color_identity; +pub const make_payload_or_string_color = union_value.make_payload_or_string_color; + +pub const Color = enum_value.Color; +pub const StringColor = enum_value.StringColor; +pub const enum_identity = enum_value.enum_identity; +pub const favorite_color = enum_value.favorite_color; +pub const is_primary = enum_value.is_primary; +pub const string_enum_identity = enum_value.string_enum_identity; +pub const favorite_string_color = enum_value.favorite_string_color; + comptime { napi.NODE_API_MODULE("hello", @This()); } diff --git a/examples/basic/src/union.zig b/examples/basic/src/union.zig new file mode 100644 index 0000000..056e5c2 --- /dev/null +++ b/examples/basic/src/union.zig @@ -0,0 +1,174 @@ +const napi = @import("napi"); +const enum_value = @import("enum.zig"); + +pub const NumberOrText = union(enum) { + number: f64, + text: []const u8, +}; + +const MessagePayload = struct { + title: []const u8, + count: f64, +}; + +pub const ObjectOrText = union(enum) { + payload: MessagePayload, + text: []const u8, +}; + +pub const ObjectOrArray = union(enum) { + payload: MessagePayload, + list: []f32, +}; + +const NamedTuple = struct { f32, bool, []const u8 }; + +pub const TupleOrText = union(enum) { + tuple: NamedTuple, + text: []const u8, +}; + +pub const FlagOrCount = union(enum) { + flag: bool, + count: f64, +}; + +pub const ColorOrText = union(enum) { + color: enum_value.Color, + text: []const u8, +}; + +pub const MaybeTextOrCount = union(enum) { + maybe_text: ?[]const u8, + count: f64, +}; + +pub const BufferOrText = union(enum) { + buffer: napi.Buffer, + text: []const u8, +}; + +pub const ArrayBufferOrArray = union(enum) { + arraybuffer: napi.ArrayBuffer, + list: []const f32, +}; + +pub const PayloadOrColor = union(enum) { + payload: MessagePayload, + color: enum_value.Color, +}; + +pub const PayloadOrStringColor = union(enum) { + payload: MessagePayload, + color: enum_value.StringColor, +}; + +pub fn union_identity(value: NumberOrText) NumberOrText { + return value; +} + +pub fn make_union(is_number: bool) NumberOrText { + if (is_number) { + return .{ .number = 42 }; + } + return .{ .text = "hello" }; +} + +pub fn union_kind(value: NumberOrText) []const u8 { + return switch (value) { + .number => "number", + .text => "text", + }; +} + +pub fn object_or_text_identity(value: ObjectOrText) ObjectOrText { + return value; +} + +pub fn make_object_or_text(as_object: bool) ObjectOrText { + if (as_object) { + return .{ .payload = .{ .title = "hello", .count = 2 } }; + } + return .{ .text = "plain" }; +} + +pub fn object_or_array_identity(value: ObjectOrArray) ObjectOrArray { + return value; +} + +pub fn tuple_or_text_identity(value: TupleOrText) TupleOrText { + return value; +} + +pub fn flip_flag_or_increment(value: FlagOrCount) FlagOrCount { + return switch (value) { + .flag => |flag| .{ .flag = !flag }, + .count => |count| .{ .count = count + 1 }, + }; +} + +pub fn color_or_text_identity(value: ColorOrText) ColorOrText { + return value; +} + +pub fn favorite_color_or_text(use_color: bool) ColorOrText { + if (use_color) { + return .{ .color = .Blue }; + } + return .{ .text = "fallback" }; +} + +pub fn maybe_text_or_count_identity(value: MaybeTextOrCount) MaybeTextOrCount { + return value; +} + +pub fn make_maybe_text_or_count(as_text: bool) MaybeTextOrCount { + if (as_text) { + return .{ .maybe_text = null }; + } + return .{ .count = 7 }; +} + +pub fn buffer_or_text_identity(value: BufferOrText) BufferOrText { + return value; +} + +pub fn make_buffer_or_text(env: napi.Env, use_buffer: bool) !BufferOrText { + if (use_buffer) { + return .{ .buffer = try napi.Buffer.New(env, 16) }; + } + return .{ .text = "buffer-fallback" }; +} + +pub fn arraybuffer_or_array_identity(value: ArrayBufferOrArray) ArrayBufferOrArray { + return value; +} + +pub fn make_arraybuffer_or_array(env: napi.Env, use_arraybuffer: bool) !ArrayBufferOrArray { + if (use_arraybuffer) { + return .{ .arraybuffer = try napi.ArrayBuffer.New(env, 16) }; + } + return .{ .list = &[_]f32{ 1, 2, 3 } }; +} + +pub fn payload_or_color_identity(value: PayloadOrColor) PayloadOrColor { + return value; +} + +pub fn make_payload_or_color(use_payload: bool) PayloadOrColor { + if (use_payload) { + return .{ .payload = .{ .title = "mixed", .count = 9 } }; + } + return .{ .color = .Red }; +} + +pub fn payload_or_string_color_identity(value: PayloadOrStringColor) PayloadOrStringColor { + return value; +} + +pub fn make_payload_or_string_color(use_payload: bool) PayloadOrStringColor { + if (use_payload) { + return .{ .payload = .{ .title = "string-enum", .count = 3 } }; + } + return .{ .color = .Green }; +} diff --git a/src/build/napi-tsgen.zig b/src/build/napi-tsgen.zig index 497651c..a1ffa9e 100644 --- a/src/build/napi-tsgen.zig +++ b/src/build/napi-tsgen.zig @@ -140,6 +140,11 @@ fn isDataViewType(comptime T: type) bool { return @hasDecl(T, "is_napi_dataview"); } +fn isStringEnumType(comptime T: type) bool { + if (@typeInfo(T) != .@"enum") return false; + return @hasDecl(T, "napi_string_enum") and @TypeOf(@field(T, "napi_string_enum")) == bool and @field(T, "napi_string_enum"); +} + fn isArrayList(comptime T: type) bool { const info = @typeInfo(T); if (info != .@"struct") return false; @@ -1003,6 +1008,9 @@ fn emitType(state: *State, comptime T: type) ![]const u8 { try emitEnumDecl(state, T); return shortTypeName(T); }, + .@"union" => { + return try emitUnionType(state, T); + }, else => {}, } @@ -1016,7 +1024,11 @@ fn emitEnumDecl(state: *State, comptime T: type) !void { try appendFmt(&state.declarations, "export declare const enum {s} {{\n", .{name}); inline for (@typeInfo(T).@"enum".fields) |field| { - try appendFmt(&state.declarations, " {s} = '{s}',\n", .{ field.name, field.name }); + if (comptime isStringEnumType(T)) { + try appendFmt(&state.declarations, " {s} = '{s}',\n", .{ field.name, field.name }); + } else { + try appendFmt(&state.declarations, " {s} = {d},\n", .{ field.name, field.value }); + } } try append(&state.declarations, "}\n\n"); } @@ -1043,6 +1055,21 @@ fn emitInterfaceDecl(state: *State, comptime T: type) !void { try append(&state.declarations, "}\n\n"); } +fn emitUnionType(state: *State, comptime T: type) ![]const u8 { + const info = @typeInfo(T).@"union"; + if (info.fields.len == 0) return "never"; + + var buf = StringBuilder.init(state.allocator); + defer buf.deinit(); + + inline for (info.fields, 0..) |field, idx| { + if (idx > 0) try append(&buf, " | "); + try append(&buf, try emitType(state, field.type)); + } + + return try buf.toOwnedSlice(); +} + fn collectFunctionInfo(comptime T: type) struct { args_type: type, return_type: type, diff --git a/src/napi/util/napi.zig b/src/napi/util/napi.zig index cb1a73b..a5e6379 100644 --- a/src/napi/util/napi.zig +++ b/src/napi/util/napi.zig @@ -11,6 +11,170 @@ const Buffer = @import("../wrapper/buffer.zig").Buffer; const ArrayBuffer = @import("../wrapper/arraybuffer.zig").ArrayBuffer; const DataView = @import("../wrapper/dataview.zig").DataView; +fn napiTypeOf(env: napi.napi_env, raw: napi.napi_value) napi.napi_valuetype { + var value_type: napi.napi_valuetype = undefined; + _ = napi.napi_typeof(env, raw, &value_type); + return value_type; +} + +fn isArrayValue(env: napi.napi_env, raw: napi.napi_value) bool { + var result = false; + _ = napi.napi_is_array(env, raw, &result); + return result; +} + +fn isBufferValue(env: napi.napi_env, raw: napi.napi_value) bool { + var result = false; + _ = napi.napi_is_buffer(env, raw, &result); + return result; +} + +fn isArrayBufferValue(env: napi.napi_env, raw: napi.napi_value) bool { + var result = false; + _ = napi.napi_is_arraybuffer(env, raw, &result); + return result; +} + +fn isTypedArrayValue(env: napi.napi_env, raw: napi.napi_value) bool { + var result = false; + _ = napi.napi_is_typedarray(env, raw, &result); + return result; +} + +fn isDataViewValue(env: napi.napi_env, raw: napi.napi_value) bool { + var result = false; + _ = napi.napi_is_dataview(env, raw, &result); + return result; +} + +fn isPromiseValue(env: napi.napi_env, raw: napi.napi_value) bool { + var result = false; + _ = napi.napi_is_promise(env, raw, &result); + return result; +} + +fn isPlainObjectValue(env: napi.napi_env, raw: napi.napi_value) bool { + if (napiTypeOf(env, raw) != napi.napi_object) return false; + if (isArrayValue(env, raw)) return false; + if (isBufferValue(env, raw)) return false; + if (isArrayBufferValue(env, raw)) return false; + if (isTypedArrayValue(env, raw)) return false; + if (isDataViewValue(env, raw)) return false; + if (isPromiseValue(env, raw)) return false; + return true; +} + +fn unionDefaultValue(comptime T: type) T { + const union_info = @typeInfo(T).@"union"; + if (union_info.fields.len == 0) { + @compileError("Union must contain at least one field"); + } + + const first = union_info.fields[0]; + return @unionInit(T, first.name, undefined); +} + +fn isStringEnum(comptime T: type) bool { + return @hasDecl(T, "napi_string_enum") and @TypeOf(@field(T, "napi_string_enum")) == bool and @field(T, "napi_string_enum"); +} + +fn enumFromString(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { + const enum_info = @typeInfo(T).@"enum"; + const value = NapiValue.String.from_napi_value(env, raw, []const u8); + + inline for (enum_info.fields) |field| { + if (std.mem.eql(u8, value, field.name)) { + return @field(T, field.name); + } + } + + NapiError.last_error = NapiError.Error{ .JsTypeError = NapiError.JsTypeError.fromMessage("Invalid enum value") }; + return @field(T, enum_info.fields[0].name); +} + +fn enumFromNumber(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { + const enum_info = @typeInfo(T).@"enum"; + const Tag = enum_info.tag_type; + const value = NapiValue.Number.from_napi_value(env, raw, Tag); + + inline for (enum_info.fields) |field| { + if (value == @as(Tag, @intCast(field.value))) { + return @field(T, field.name); + } + } + + NapiError.last_error = NapiError.Error{ .JsTypeError = NapiError.JsTypeError.fromMessage("Invalid enum value") }; + return @field(T, enum_info.fields[0].name); +} + +fn enumTypeToObject(env: napi.napi_env, comptime E: type) !napi.napi_value { + var raw: napi.napi_value = undefined; + const status = napi.napi_create_object(env, &raw); + if (status != napi.napi_ok) { + return NapiError.Error.fromStatus(NapiError.Status.New(status)); + } + + const object = NapiValue.Object.from_raw(env, raw); + inline for (@typeInfo(E).@"enum".fields) |field| { + if (comptime isStringEnum(E)) { + try object.Set(field.name, field.name); + } else { + const Tag = @typeInfo(E).@"enum".tag_type; + try object.Set(field.name, @as(Tag, @intCast(field.value))); + } + } + return raw; +} + +fn valueMatchesType(env: napi.napi_env, raw: napi.napi_value, comptime T: type) bool { + switch (T) { + NapiValue.Number => return napiTypeOf(env, raw) == napi.napi_number, + NapiValue.String => return napiTypeOf(env, raw) == napi.napi_string, + NapiValue.Bool => return napiTypeOf(env, raw) == napi.napi_boolean, + NapiValue.Object => return isPlainObjectValue(env, raw), + NapiValue.Promise => return isPromiseValue(env, raw), + NapiValue.Array => return isArrayValue(env, raw) or isTypedArrayValue(env, raw), + NapiValue.Undefined => return napiTypeOf(env, raw) == napi.napi_undefined, + NapiValue.Null => return napiTypeOf(env, raw) == napi.napi_null, + Buffer => return isBufferValue(env, raw), + ArrayBuffer => return isArrayBufferValue(env, raw), + DataView => return isDataViewValue(env, raw), + else => {}, + } + + const string_mode = comptime helper.stringLike(T); + if (string_mode != .Unknown) { + return napiTypeOf(env, raw) == napi.napi_string; + } + + const infos = @typeInfo(T); + return switch (infos) { + .float, .int, .comptime_int, .comptime_float => napiTypeOf(env, raw) == napi.napi_number, + .bool => napiTypeOf(env, raw) == napi.napi_boolean, + .array => isArrayValue(env, raw) or isTypedArrayValue(env, raw), + .pointer => helper.isSlice(T) and (isArrayValue(env, raw) or isTypedArrayValue(env, raw)), + .optional => blk: { + const value_type = napiTypeOf(env, raw); + if (value_type == napi.napi_null or value_type == napi.napi_undefined) { + break :blk true; + } + break :blk valueMatchesType(env, raw, infos.optional.child); + }, + .@"struct" => blk: { + if (comptime helper.isNapiFunction(T)) break :blk napiTypeOf(env, raw) == napi.napi_function; + if (comptime helper.isTypedArray(T)) break :blk isTypedArrayValue(env, raw); + if (comptime helper.isDataView(T)) break :blk isDataViewValue(env, raw); + if (comptime helper.isReference(T)) break :blk true; + if (comptime helper.isTuple(T)) break :blk isArrayValue(env, raw); + if (comptime helper.isArrayList(T)) break :blk isArrayValue(env, raw) or isTypedArrayValue(env, raw); + break :blk isPlainObjectValue(env, raw); + }, + .@"union" => infos.@"union".tag_type != null, + .@"enum" => if (comptime isStringEnum(T)) napiTypeOf(env, raw) == napi.napi_string else napiTypeOf(env, raw) == napi.napi_number, + else => false, + }; +} + pub const Napi = struct { pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { const infos = @typeInfo(T); @@ -113,20 +277,38 @@ pub const Napi = struct { .bool => { return NapiValue.Bool.from_napi_value(env, raw, T); }, + .@"enum" => { + if (comptime isStringEnum(T)) { + return enumFromString(env, raw, T); + } + return enumFromNumber(env, raw, T); + }, .optional => { - var value_type: napi.napi_valuetype = undefined; - - _ = napi.napi_typeof(env, raw, &value_type); + const value_type = napiTypeOf(env, raw); switch (value_type) { napi.napi_null, napi.napi_undefined => { return null; }, else => { - return Napi.from_napi_value(env, raw, T); + return Napi.from_napi_value(env, raw, infos.optional.child); }, } }, + .@"union" => { + if (infos.@"union".tag_type == null) { + @compileError("Only tagged union(enum) is supported, got: " ++ @typeName(T)); + } + + inline for (infos.@"union".fields) |field| { + if (valueMatchesType(env, raw, field.type)) { + return @unionInit(T, field.name, Napi.from_napi_value(env, raw, field.type)); + } + } + + NapiError.last_error = NapiError.Error{ .JsTypeError = NapiError.JsTypeError.fromMessage("Value does not match any supported union variant") }; + return unionDefaultValue(T); + }, else => { const hasFromRaw = @hasField(T, "from_raw"); if (!hasFromRaw) { @@ -154,6 +336,9 @@ pub const Napi = struct { return value; }, else => { + if (comptime value_type == type and @typeInfo(value) == .@"enum") { + return try enumTypeToObject(env, value); + } switch (infos) { .@"fn" => { const fn_name = name orelse @typeName(value_type); @@ -231,6 +416,12 @@ pub const Napi = struct { .bool => { return NapiValue.Bool.New(Env.from_raw(env), value).raw; }, + .@"enum" => { + if (comptime isStringEnum(value_type)) { + return NapiValue.String.New(Env.from_raw(env), @tagName(value)).raw; + } + return NapiValue.Number.New(Env.from_raw(env), @intFromEnum(value)).raw; + }, .optional => { if (value) |v| { if (@typeInfo(@TypeOf(v)) == .null) { @@ -240,6 +431,15 @@ pub const Napi = struct { } return NapiValue.Undefined.New(Env.from_raw(env)).raw; }, + .@"union" => { + if (infos.@"union".tag_type == null) { + @compileError("Only tagged union(enum) is supported, got: " ++ @typeName(value_type)); + } + + return switch (value) { + inline else => |payload| try Napi.to_napi_value(env, payload, name), + }; + }, else => { const stringMode = comptime helper.stringLike(value_type); switch (stringMode) { diff --git a/src/napi/value/function.zig b/src/napi/value/function.zig index 7b39eeb..20b77cf 100644 --- a/src/napi/value/function.zig +++ b/src/napi/value/function.zig @@ -57,7 +57,16 @@ pub fn Function(comptime Args: type, comptime Return: type) type { } inline for (params[env_index..], env_index..) |param_index, i| { + if (comptime @typeInfo(param_index.type.?) == .@"union") { + NapiError.clearLastError(); + } napi_params[i] = Napi.from_napi_value(inner_env, args_raw[i - env_index], param_index.type.?); + if (comptime @typeInfo(param_index.type.?) == .@"union") { + if (NapiError.last_error) |last_err| { + last_err.throwInto(Env.from_raw(inner_env)); + return undefined_value.raw; + } + } } const return_info = infos.@"fn".return_type.?; @@ -134,6 +143,14 @@ pub fn Function(comptime Args: type, comptime Return: type) type { return NapiError.Error.fromStatus(NapiError.Status.New(status)); } + if (comptime @typeInfo(Return) == .@"union") { + NapiError.clearLastError(); + const converted = Napi.from_napi_value(self.env, result, Return); + if (NapiError.last_error) |_| { + return error.GenericFailure; + } + return converted; + } return Napi.from_napi_value(self.env, result, Return); } diff --git a/src/napi/value/number.zig b/src/napi/value/number.zig index 44b49c3..c4c3e80 100644 --- a/src/napi/value/number.zig +++ b/src/napi/value/number.zig @@ -17,32 +17,36 @@ pub const Number = struct { } pub fn from_napi_value(env: napi.napi_env, raw: napi.napi_value, comptime T: type) T { - switch (T) { - f16, f32, f64 => { - var result: T = undefined; + switch (@typeInfo(T)) { + .float => { var temp: f64 = undefined; _ = napi.napi_get_value_double(env, raw, &temp); - result = @floatCast(temp); - return result; - }, - isize, i8, i16, i32 => { - var result: T = undefined; - var temp: i32 = undefined; - _ = napi.napi_get_value_int32(env, raw, &temp); - result = @intCast(temp); - return result; - }, - usize, u8, u16, u32 => { - var result: T = undefined; - var temp: u32 = undefined; - _ = napi.napi_get_value_uint32(env, raw, &temp); - result = @intCast(temp); - return result; + return @floatCast(temp); }, + .int => |int| { + if (int.signedness == .signed) { + if (int.bits <= 32) { + var temp: i32 = undefined; + _ = napi.napi_get_value_int32(env, raw, &temp); + return @intCast(temp); + } - else => { - @compileError("Unsupported type: " ++ @typeName(T)); + var temp: i64 = undefined; + _ = napi.napi_get_value_int64(env, raw, &temp); + return @intCast(temp); + } + + if (int.bits <= 32) { + var temp: u32 = undefined; + _ = napi.napi_get_value_uint32(env, raw, &temp); + return @intCast(temp); + } + + var temp: i64 = undefined; + _ = napi.napi_get_value_int64(env, raw, &temp); + return @intCast(temp); }, + else => @compileError("Unsupported type: " ++ @typeName(T)), } } diff --git a/src/napi/wrapper/class.zig b/src/napi/wrapper/class.zig index 5595194..d93689d 100644 --- a/src/napi/wrapper/class.zig +++ b/src/napi/wrapper/class.zig @@ -44,7 +44,17 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { var tuple_args: std.meta.ArgsTuple(init_fn_type) = undefined; inline for (init_fn_info.@"fn".params, 0..) |arg, i| { + if (comptime @typeInfo(arg.type.?) == .@"union") { + NapiError.clearLastError(); + } tuple_args[i] = Napi.from_napi_value(infos.env, infos.args[i].raw, arg.type.?); + if (comptime @typeInfo(arg.type.?) == .@"union") { + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(env)); + GlobalAllocator.globalAllocator().destroy(data); + return null; + } + } } if (@typeInfo(init_fn_info.@"fn".return_type.?) == .error_union) { @@ -62,7 +72,17 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { data.* = std.mem.zeroes(T); if (comptime HasInit) { inline for (fields, 0..) |field, i| { + if (comptime @typeInfo(field.type) == .@"union") { + NapiError.clearLastError(); + } @field(data.*, field.name) = Napi.from_napi_value(env, infos.args[i].raw, field.type); + if (comptime @typeInfo(field.type) == .@"union") { + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(env)); + GlobalAllocator.globalAllocator().destroy(data); + return null; + } + } } } } @@ -100,7 +120,16 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { var tuple_args: std.meta.ArgsTuple(factory_fn_type) = undefined; inline for (params, 0..) |param, i| { if (i < infos.args.len) { + if (comptime @typeInfo(param.type.?) == .@"union") { + NapiError.clearLastError(); + } tuple_args[i] = Napi.from_napi_value(infos.env, infos.args[i].raw, param.type.?); + if (comptime @typeInfo(param.type.?) == .@"union") { + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(env)); + return null; + } + } } } if (@typeInfo(factory_fn_info.@"fn".return_type.?) == .error_union) { @@ -212,7 +241,16 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { const instance: *T = @ptrCast(@alignCast(data.?)); const args = cb_info.args; if (args.len > 0) { + if (comptime @typeInfo(field.type) == .@"union") { + NapiError.clearLastError(); + } const new_value = Napi.from_napi_value(setter_env, args[0].raw, field.type); + if (comptime @typeInfo(field.type) == .@"union") { + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(setter_env)); + return null; + } + } @field(instance.*, field.name) = new_value; } return null; @@ -327,7 +365,16 @@ pub fn ClassWrapper(comptime T: type, comptime HasInit: bool) type { const args_offset = if (is_instance_method) 1 else 0; // inject args inline for (method_info.@"fn".params[args_offset..], args_offset..) |param, i| { + if (comptime @typeInfo(param.type.?) == .@"union") { + NapiError.clearLastError(); + } tuple_args[i] = Napi.from_napi_value(method_env, cb_info.args[i - args_offset].raw, param.type.?); + if (comptime @typeInfo(param.type.?) == .@"union") { + if (NapiError.last_error) |last_err| { + last_err.throwInto(napi_env.Env.from_raw(method_env)); + return null; + } + } } const result = @call(.auto, method, tuple_args); return Napi.to_napi_value(method_env, result, fn_name) catch null; diff --git a/src/napi/wrapper/error.zig b/src/napi/wrapper/error.zig index 185a189..e36aa1d 100644 --- a/src/napi/wrapper/error.zig +++ b/src/napi/wrapper/error.zig @@ -9,6 +9,11 @@ pub const Status = @import("status.zig").Status; pub threadlocal var last_error: ?Error = null; pub threadlocal var last_error_status: ?Status = null; +pub fn clearLastError() void { + last_error = null; + last_error_status = null; +} + pub const ErrorStatus = error{ InvalidArg, ObjectExpected,