From 74e7cf7a4c6ccb34c00300f83369282c9abaa4bf Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Mon, 21 Jul 2025 18:25:14 -0400 Subject: [PATCH 1/9] feat: add pushNumeric, toNumeric and checkNumeric These functions rely (lightly) on Zig's comptime to minimize some of the annoying overhead of writing `@intCast` and `@floatCast` all the time by making those builtins part of the function definition. However, since those builtins assert in builds with runtime safety enabled, these functions will crash the program if called with bad Lua input. More discussion is warranted about the tradeoffs of going this route before merging. Resolves #172. --- src/lib.zig | 38 ++++++++++++++++++++++++++++++++++++++ src/tests.zig | 10 ++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/lib.zig b/src/lib.zig index 53df4c1..00a3709 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -2035,6 +2035,18 @@ pub const Lua = opaque { c.lua_pushnil(@ptrCast(lua)); } + /// Pushes a numeric type with value `n` onto the stack + /// The conversion from the type of `n` to `Integer` or `Number` + /// is performed with `@_Cast` so will assert in modes with runtime safety enabled. + /// + /// * Pops: `0` + /// * Pushes: `1` + /// * Errors: `never` + pub fn pushNumeric(lua: *Lua, n: anytype) void { + if (@typeInfo(@TypeOf(n)) == .int) return lua.pushInteger(@intCast(n)); + lua.pushNumber(@floatCast(n)); + } + /// Pushes a float with value `n` onto the stack /// /// * Pops: `0` @@ -2594,6 +2606,19 @@ pub const Lua = opaque { c.lua_toclose(@ptrCast(lua), index); } + /// Converts the Lua value at the given `index` to a numeric type; + /// if T is an integer type, the Lua value is converted to an integer. + /// The conversion from `Integer` or `Number` to T is performed with `@_Cast`, + /// which will assert in builds with runtime safety enabled + /// + /// * Pops: `0` + /// * Pushes: `0` + /// * Errors: `never` + pub fn toNumeric(lua: *Lua, comptime T: type, index: i32) !T { + if (@typeInfo(T) == .int) return @intCast(try lua.toInteger(index)); + return @floatCast(try lua.toNumber(index)); + } + /// Converts the Lua value at the given `index` to a signed integer /// The Lua value must be an integer, or a number, or a string convertible to an integer /// Returns an error if the conversion failed @@ -3342,6 +3367,19 @@ pub const Lua = opaque { c.luaL_checkany(@ptrCast(lua), arg); } + /// Checks whether the function argument `arg` is a numeric type and converts it to type T + /// + /// The conversion is done with `@intCast` for numeric types, + /// so causes an assertion in modes with runtime safety enabled. + /// + /// * Pops: `0` + /// * Pushes: `0` + /// * Errors: `explained in text / on purpose` + pub fn checkNumeric(lua: *Lua, comptime T: type, arg: i32) T { + if (@typeInfo(T) == .int) return @intCast(lua.checkInteger(arg)); + return @floatCast(lua.checkNumber(arg)); + } + /// Checks whether the function argument `arg` is a number and returns this number cast to an i32 /// /// Not available in Lua 5.3 and 5.4 diff --git a/src/tests.zig b/src/tests.zig index 21cf279..6ee274c 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3055,3 +3055,13 @@ test "error union for CFn" { try expectEqualStrings("MissingInteger", try lua.toString(-1)); }; } + +test "pushNumeric and toNumeric" { + const lua: *Lua = try .init(testing.allocator); + defer lua.deinit(); + + const num: u32 = 100; + lua.pushNumeric(num); + const pull = lua.toNumeric(u32, lua.getTop()); + try std.testing.expectEqual(num, pull); +} From 0ffdfc05d6853cb6e93f4954905e5aff866e1b7d Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 09:54:01 -0400 Subject: [PATCH 2/9] fix: remove pushNumeric --- src/lib.zig | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 00a3709..3355658 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -2035,18 +2035,6 @@ pub const Lua = opaque { c.lua_pushnil(@ptrCast(lua)); } - /// Pushes a numeric type with value `n` onto the stack - /// The conversion from the type of `n` to `Integer` or `Number` - /// is performed with `@_Cast` so will assert in modes with runtime safety enabled. - /// - /// * Pops: `0` - /// * Pushes: `1` - /// * Errors: `never` - pub fn pushNumeric(lua: *Lua, n: anytype) void { - if (@typeInfo(@TypeOf(n)) == .int) return lua.pushInteger(@intCast(n)); - lua.pushNumber(@floatCast(n)); - } - /// Pushes a float with value `n` onto the stack /// /// * Pops: `0` From 3ea22a692964b3ef1d21d65efb2555cdeb8f1df6 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 09:54:43 -0400 Subject: [PATCH 3/9] fix: checkNumeric raises a lua error --- src/lib.zig | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 3355658..6ae3395 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3357,14 +3357,26 @@ pub const Lua = opaque { /// Checks whether the function argument `arg` is a numeric type and converts it to type T /// - /// The conversion is done with `@intCast` for numeric types, - /// so causes an assertion in modes with runtime safety enabled. + /// Raises a Lua error if the argument is an integer type but std.math.cast fails /// /// * Pops: `0` /// * Pushes: `0` /// * Errors: `explained in text / on purpose` pub fn checkNumeric(lua: *Lua, comptime T: type, arg: i32) T { - if (@typeInfo(T) == .int) return @intCast(lua.checkInteger(arg)); + if (@typeInfo(T) == .int) return std.math.cast(T, lua.checkNumber(arg)) orelse { + const error_msg = comptime msg: { + var buf: [1024]u8 = undefined; + const info = @typeInfo(T).int; + const signedness = switch (info.signedness) { + .unsigned => "u", + .signed => "i", + }; + break :msg std.fmt.bufPrintZ(&buf, "Integer argument doesn't fit inside {s}{d} range [{d}, {d}]", .{ + signedness, info.bits, std.math.minInt(T), std.math.maxInt(T), + }) catch unreachable; + }; + lua.argError(arg, error_msg); + }; return @floatCast(lua.checkNumber(arg)); } From ffa220566cb8e06b055c64c5a8c8e729a178e6b3 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 09:59:25 -0400 Subject: [PATCH 4/9] fix: toNumeric raises a Zig error --- src/lib.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 6ae3395..143bf67 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -2596,14 +2596,12 @@ pub const Lua = opaque { /// Converts the Lua value at the given `index` to a numeric type; /// if T is an integer type, the Lua value is converted to an integer. - /// The conversion from `Integer` or `Number` to T is performed with `@_Cast`, - /// which will assert in builds with runtime safety enabled /// /// * Pops: `0` /// * Pushes: `0` - /// * Errors: `never` + /// * Errors: `error.IntegerCastFailed` if `T` is an integer type and the value at index doesn't fit pub fn toNumeric(lua: *Lua, comptime T: type, index: i32) !T { - if (@typeInfo(T) == .int) return @intCast(try lua.toInteger(index)); + if (@typeInfo(T) == .int) return std.math.cast(try lua.toInteger(index)) orelse error.IntegerCastFailed; return @floatCast(try lua.toNumber(index)); } From 84076570f0534774ab0dbe4ac82a0f867db3d90d Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 10:02:25 -0400 Subject: [PATCH 5/9] fix: remove pushNumeric from test, typo in toNumeric --- src/lib.zig | 2 +- src/tests.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 143bf67..a2a94be 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -2601,7 +2601,7 @@ pub const Lua = opaque { /// * Pushes: `0` /// * Errors: `error.IntegerCastFailed` if `T` is an integer type and the value at index doesn't fit pub fn toNumeric(lua: *Lua, comptime T: type, index: i32) !T { - if (@typeInfo(T) == .int) return std.math.cast(try lua.toInteger(index)) orelse error.IntegerCastFailed; + if (@typeInfo(T) == .int) return std.math.cast(T, try lua.toInteger(index)) orelse error.IntegerCastFailed; return @floatCast(try lua.toNumber(index)); } diff --git a/src/tests.zig b/src/tests.zig index 6ee274c..05ff570 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3061,7 +3061,7 @@ test "pushNumeric and toNumeric" { defer lua.deinit(); const num: u32 = 100; - lua.pushNumeric(num); + lua.pushInteger(num); const pull = lua.toNumeric(u32, lua.getTop()); try std.testing.expectEqual(num, pull); } From 436ceb743560b1d6c514ed6e8d9968f4254cc200 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 14:54:29 -0400 Subject: [PATCH 6/9] fix: error.IntegerCastFailed -> error.Overflow --- src/lib.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index a2a94be..7c2e01b 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -2599,9 +2599,11 @@ pub const Lua = opaque { /// /// * Pops: `0` /// * Pushes: `0` - /// * Errors: `error.IntegerCastFailed` if `T` is an integer type and the value at index doesn't fit + /// * Errors: `error.Overflow` if `T` is an integer type and the value at index doesn't fit pub fn toNumeric(lua: *Lua, comptime T: type, index: i32) !T { - if (@typeInfo(T) == .int) return std.math.cast(T, try lua.toInteger(index)) orelse error.IntegerCastFailed; + if (@typeInfo(T) == .int) { + return std.math.cast(T, try lua.toInteger(index)) orelse error.Overflow; + } return @floatCast(try lua.toNumber(index)); } From 47d4e389316ad36278e29d8dc1de0cc6e006cc92 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 14:55:36 -0400 Subject: [PATCH 7/9] fix: add test for checkNumeric --- src/lib.zig | 9 +++++---- src/tests.zig | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 7c2e01b..14adbdb 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3363,7 +3363,8 @@ pub const Lua = opaque { /// * Pushes: `0` /// * Errors: `explained in text / on purpose` pub fn checkNumeric(lua: *Lua, comptime T: type, arg: i32) T { - if (@typeInfo(T) == .int) return std.math.cast(T, lua.checkNumber(arg)) orelse { + if (comptime @typeInfo(T) != .int) return @floatCast(lua.checkNumber(arg)); + return std.math.cast(T, lua.checkInteger(arg)) orelse { const error_msg = comptime msg: { var buf: [1024]u8 = undefined; const info = @typeInfo(T).int; @@ -3371,13 +3372,13 @@ pub const Lua = opaque { .unsigned => "u", .signed => "i", }; - break :msg std.fmt.bufPrintZ(&buf, "Integer argument doesn't fit inside {s}{d} range [{d}, {d}]", .{ + const output = std.fmt.bufPrintZ(&buf, "integer argument doesn't fit inside {s}{d} range [{d}, {d}]", .{ signedness, info.bits, std.math.minInt(T), std.math.maxInt(T), }) catch unreachable; + break :msg output[0..output.len :0].*; }; - lua.argError(arg, error_msg); + lua.argError(arg, &error_msg); }; - return @floatCast(lua.checkNumber(arg)); } /// Checks whether the function argument `arg` is a number and returns this number cast to an i32 diff --git a/src/tests.zig b/src/tests.zig index 05ff570..694afc1 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3056,12 +3056,38 @@ test "error union for CFn" { }; } -test "pushNumeric and toNumeric" { +test "checkNumeric and toNumeric" { + const error_msg = "integer argument doesn't fit inside u8 range [0, 255]"; + const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); - const num: u32 = 100; - lua.pushInteger(num); - const pull = lua.toNumeric(u32, lua.getTop()); - try std.testing.expectEqual(num, pull); + lua.pushFunction(zlua.wrap(struct { + fn f(l: *Lua) i32 { + _ = l.checkNumeric(u8, 1); + return 1; + } + }.f)); + const idx = lua.getTop(); + + lua.pushValue(idx); + lua.pushInteger(128); + try lua.protectedCall(.{ + .args = 1, + .results = 1, + }); + const val = lua.toNumeric(u8, lua.getTop()); + try std.testing.expectEqual(128, val); + + lua.pushValue(idx); + lua.pushInteger(256); + if (lua.protectedCall(.{ + .args = 1, + .results = 0, + })) |_| { + return error.ExpectedError; + } else |_| { + const string = lua.toStringEx(lua.getTop()); + try std.testing.expectEqualStrings(error_msg, string); + } } From 16806d3c0c4f5bc31b868a9a23fa44d373d09f8a Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 19:03:23 -0400 Subject: [PATCH 8/9] fix: error message inconsistency outside of test control --- src/tests.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tests.zig b/src/tests.zig index 694afc1..ba62ea6 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3088,6 +3088,8 @@ test "checkNumeric and toNumeric" { return error.ExpectedError; } else |_| { const string = lua.toStringEx(lua.getTop()); - try std.testing.expectEqualStrings(error_msg, string); + errdefer std.log.err("expected error message to contain: {s}", .{error_msg}); + errdefer std.log.err("error message: {s}", .{string}); + _ = std.mem.indexOf(u8, string, error_msg) orelse return error.BadErrorMessage; } } From 9d2f73e93935eecd214625b8072378cde549eccc Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Tue, 19 Aug 2025 19:07:29 -0400 Subject: [PATCH 9/9] fix: toStringEx not available in 5.1 --- src/lib.zig | 307 +++++++++++++++++++++++--------------------------- src/tests.zig | 2 +- 2 files changed, 144 insertions(+), 165 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 14adbdb..d3df793 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -5157,21 +5157,30 @@ pub const Buffer = struct { // Helper functions to make the zlua API easier to use -const Tuple = std.meta.Tuple; - fn TypeOfWrap(comptime function: anytype) type { - const Args = std.meta.ArgsTuple(@TypeOf(function)); - return switch (Args) { - Tuple(&.{*Lua}) => CFn, - Tuple(&.{ *Lua, Event, *DebugInfo }) => CHookFn, - Tuple(&.{ *Lua, Status, Context }) => CContFn, - Tuple(&.{ *Lua, *anyopaque }) => CReaderFn, - Tuple(&.{*anyopaque}) => CUserdataDtorFn, - Tuple(&.{ *Lua, i32 }) => CInterruptCallbackFn, - Tuple(&.{[]const u8}) => CUserAtomCallbackFn, - Tuple(&.{ ?*anyopaque, []const u8, bool }) => CWarnFn, - Tuple(&.{ *Lua, []const u8, *anyopaque }) => CWriterFn, - else => @compileError("Unsupported function given to wrap '" ++ @typeName(@TypeOf(function)) ++ "'"), + const params = @typeInfo(@TypeOf(function)).@"fn".params; + if (params.len == 1) { + if (params[0].type.? == *Lua) return CFn; + if (params[0].type.? == []const u8) return CUserAtomCallbackFn; + if (params[0].type.? == *anyopaque) return CUserdataDtorFn; + } + if (params.len == 2) { + if (params[0].type.? == *Lua) { + if (params[1].type.? == i32) return CInterruptCallbackFn; + if (params[1].type.? == *anyopaque) return CReaderFn; + } + } + if (params.len == 3) { + if (params[0].type.? == ?*anyopaque and params[1].type.? == []const u8 and params[2].type.? == bool) return CWarnFn; + if (params[0].type.? == *Lua) { + if (params[1].type.? == Event and params[2].type.? == *DebugInfo) return CHookFn; + if (params[1].type.? == Status and params[2].type.? == Context) return CContFn; + if (params[1].type.? == []const u8 and params[2].type.? == *anyopaque) return CWriterFn; + } + } + return { + @compileLog(@TypeOf(function)); + @compileError("Unsupported function given to wrap."); }; } @@ -5190,169 +5199,139 @@ fn TypeOfWrap(comptime function: anytype) type { /// Functions that accept a `*Lua` pointer also support returning error unions. For example, /// wrap also supports `fn (lua: *Lua) !i32` for a `CFn`. pub fn wrap(comptime function: anytype) TypeOfWrap(function) { - const info = @typeInfo(@TypeOf(function)); - if (info != .@"fn") { - @compileError("Wrap only accepts functions"); - } - - const has_error_union = @typeInfo(info.@"fn".return_type.?) == .error_union; - - const Args = std.meta.ArgsTuple(@TypeOf(function)); - switch (Args) { - // CFn - Tuple(&.{*Lua}) => { - return struct { - fn inner(state: ?*LuaState) callconv(.c) c_int { - // this is called by Lua, state should never be null - var lua: *Lua = @ptrCast(state.?); - if (has_error_union) { - return @call(.always_inline, function, .{lua}) catch |err| { - lua.raiseErrorStr(@errorName(err), .{}); - }; - } else { - return @call(.always_inline, function, .{lua}); - } + const info = @typeInfo(@TypeOf(function)).@"fn"; + + const has_error_union = @typeInfo(info.return_type.?) == .error_union; + + const Return = TypeOfWrap(function); + return switch (Return) { + CFn => struct { + fn inner(state: ?*LuaState) callconv(.c) c_int { + // this is called by Lua, state should never be null + var lua: *Lua = @ptrCast(state.?); + if (has_error_union) { + return @call(.always_inline, function, .{lua}) catch |err| { + lua.raiseErrorStr(@errorName(err), .{}); + }; + } else { + return @call(.always_inline, function, .{lua}); } - }.inner; - }, - // CHookFn - Tuple(&.{ *Lua, Event, *DebugInfo }) => { - return struct { - fn inner(state: ?*LuaState, ar: ?*Debug) callconv(.c) void { - // this is called by Lua, state should never be null - var lua: *Lua = @ptrCast(state.?); - var debug_info: DebugInfo = .{ - .current_line = if (ar.?.currentline == -1) null else ar.?.currentline, - .private = switch (lang) { - .lua51, .luajit => ar.?.i_ci, - else => @ptrCast(ar.?.i_ci), - }, + } + }.inner, + CHookFn => struct { + fn inner(state: ?*LuaState, ar: ?*Debug) callconv(.c) void { + // this is called by Lua, state should never be null + var lua: *Lua = @ptrCast(state.?); + var debug_info: DebugInfo = .{ + .current_line = if (ar.?.currentline == -1) null else ar.?.currentline, + .private = switch (lang) { + .lua51, .luajit => ar.?.i_ci, + else => @ptrCast(ar.?.i_ci), + }, + }; + if (has_error_union) { + @call(.always_inline, function, .{ lua, @as(Event, @enumFromInt(ar.?.event)), &debug_info }) catch |err| { + lua.raiseErrorStr(@errorName(err), .{}); }; - if (has_error_union) { - @call(.always_inline, function, .{ lua, @as(Event, @enumFromInt(ar.?.event)), &debug_info }) catch |err| { - lua.raiseErrorStr(@errorName(err), .{}); - }; - } else { - @call(.always_inline, function, .{ lua, @as(Event, @enumFromInt(ar.?.event)), &debug_info }); - } + } else { + @call(.always_inline, function, .{ lua, @as(Event, @enumFromInt(ar.?.event)), &debug_info }); } - }.inner; - }, - // CContFn - Tuple(&.{ *Lua, Status, Context }) => { - return struct { - fn inner(state: ?*LuaState, status: c_int, ctx: Context) callconv(.c) c_int { - // this is called by Lua, state should never be null - var lua: *Lua = @ptrCast(state.?); - if (has_error_union) { - return @call(.always_inline, function, .{ lua, @as(Status, @enumFromInt(status)), ctx }) catch |err| { - lua.raiseErrorStr(@errorName(err), .{}); - }; - } else { - return @call(.always_inline, function, .{ lua, @as(Status, @enumFromInt(status)), ctx }); - } + } + }.inner, + CContFn => struct { + fn inner(state: ?*LuaState, status: c_int, ctx: Context) callconv(.c) c_int { + // this is called by Lua, state should never be null + var lua: *Lua = @ptrCast(state.?); + if (has_error_union) { + return @call(.always_inline, function, .{ lua, @as(Status, @enumFromInt(status)), ctx }) catch |err| { + lua.raiseErrorStr(@errorName(err), .{}); + }; + } else { + return @call(.always_inline, function, .{ lua, @as(Status, @enumFromInt(status)), ctx }); } - }.inner; - }, - // CReaderFn - Tuple(&.{ *Lua, *anyopaque }) => { - return struct { - fn inner(state: ?*LuaState, data: ?*anyopaque, size: [*c]usize) callconv(.c) [*c]const u8 { - // this is called by Lua, state should never be null - var lua: *Lua = @ptrCast(state.?); - if (has_error_union) { - const result = @call(.always_inline, function, .{ lua, data.? }) catch |err| { - lua.raiseErrorStr(@errorName(err), .{}); - }; - if (result) |buffer| { - size.* = buffer.len; - return buffer.ptr; - } else { - size.* = 0; - return null; - } + } + }.inner, + CReaderFn => struct { + fn inner(state: ?*LuaState, data: ?*anyopaque, size: [*c]usize) callconv(.c) [*c]const u8 { + // this is called by Lua, state should never be null + var lua: *Lua = @ptrCast(state.?); + if (has_error_union) { + const result = @call(.always_inline, function, .{ lua, data.? }) catch |err| { + lua.raiseErrorStr(@errorName(err), .{}); + }; + if (result) |buffer| { + size.* = buffer.len; + return buffer.ptr; } else { - if (@call(.always_inline, function, .{ lua, data.? })) |buffer| { - size.* = buffer.len; - return buffer.ptr; - } else { - size.* = 0; - return null; - } + size.* = 0; + return null; } - } - }.inner; - }, - // CUserdataDtorFn - Tuple(&.{*anyopaque}) => { - return struct { - fn inner(userdata: *anyopaque) callconv(.c) void { - return @call(.always_inline, function, .{userdata}); - } - }.inner; - }, - // CInterruptCallbackFn - Tuple(&.{ *Lua, i32 }) => { - return struct { - fn inner(state: ?*LuaState, gc: c_int) callconv(.c) void { - // this is called by Lua, state should never be null - var lua: *Lua = @ptrCast(state.?); - if (has_error_union) { - @call(.always_inline, function, .{ lua, gc }) catch |err| { - lua.raiseErrorStr(@errorName(err), .{}); - }; + } else { + if (@call(.always_inline, function, .{ lua, data.? })) |buffer| { + size.* = buffer.len; + return buffer.ptr; } else { - @call(.always_inline, function, .{ lua, gc }); + size.* = 0; + return null; } } - }.inner; - }, - // CUserAtomCallbackFn - Tuple(&.{[]const u8}) => { - return struct { - fn inner(str: [*c]const u8, len: usize) callconv(.c) i16 { - if (str) |s| { - const buf = s[0..len]; - return @call(.always_inline, function, .{buf}); - } - return -1; + } + }.inner, + CUserdataDtorFn => struct { + fn inner(userdata: *anyopaque) callconv(.c) void { + return @call(.always_inline, function, .{userdata}); + } + }.inner, + CInterruptCallbackFn => struct { + fn inner(state: ?*LuaState, gc: c_int) callconv(.c) void { + // this is called by Lua, state should never be null + var lua: *Lua = @ptrCast(state.?); + if (has_error_union) { + @call(.always_inline, function, .{ lua, gc }) catch |err| { + lua.raiseErrorStr(@errorName(err), .{}); + }; + } else { + @call(.always_inline, function, .{ lua, gc }); } - }.inner; - }, - // CWarnFn - Tuple(&.{ ?*anyopaque, []const u8, bool }) => { - return struct { - fn inner(data: ?*anyopaque, msg: [*c]const u8, to_cont: c_int) callconv(.c) void { - // warning messages emitted from Lua should be null-terminated for display - const message = std.mem.span(@as([*:0]const u8, @ptrCast(msg))); - @call(.always_inline, function, .{ data, message, to_cont != 0 }); + } + }.inner, + CUserAtomCallbackFn => struct { + fn inner(str: [*c]const u8, len: usize) callconv(.c) i16 { + if (str) |s| { + const buf = s[0..len]; + return @call(.always_inline, function, .{buf}); } - }.inner; - }, - // CWriterFn - Tuple(&.{ *Lua, []const u8, *anyopaque }) => { - return struct { - fn inner(state: ?*LuaState, buf: ?*const anyopaque, size: usize, data: ?*anyopaque) callconv(.c) c_int { - // this is called by Lua, state should never be null - var lua: *Lua = @ptrCast(state.?); - const buffer = @as([*]const u8, @ptrCast(buf))[0..size]; - - const result = if (has_error_union) blk: { - break :blk @call(.always_inline, function, .{ lua, buffer, data.? }) catch |err| { - lua.raiseErrorStr(@errorName(err), .{}); - }; - } else blk: { - break :blk @call(.always_inline, function, .{ lua, buffer, data.? }); + return -1; + } + }.inner, + CWarnFn => struct { + fn inner(data: ?*anyopaque, msg: [*c]const u8, to_cont: c_int) callconv(.c) void { + // warning messages emitted from Lua should be null-terminated for display + const message = std.mem.span(@as([*:0]const u8, @ptrCast(msg))); + @call(.always_inline, function, .{ data, message, to_cont != 0 }); + } + }.inner, + CWriterFn => struct { + fn inner(state: ?*LuaState, buf: ?*const anyopaque, size: usize, data: ?*anyopaque) callconv(.c) c_int { + // this is called by Lua, state should never be null + var lua: *Lua = @ptrCast(state.?); + const buffer = @as([*]const u8, @ptrCast(buf))[0..size]; + + const result = if (has_error_union) blk: { + break :blk @call(.always_inline, function, .{ lua, buffer, data.? }) catch |err| { + lua.raiseErrorStr(@errorName(err), .{}); }; + } else blk: { + break :blk @call(.always_inline, function, .{ lua, buffer, data.? }); + }; - // it makes more sense for the inner writer function to return false for failure, - // so negate the result here - return @intFromBool(!result); - } - }.inner; - }, - else => @compileError("Unsupported function given to wrap '" ++ @typeName(@TypeOf(function)) ++ "'"), - } + // it makes more sense for the inner writer function to return false for failure, + // so negate the result here + return @intFromBool(!result); + } + }.inner, + else => unreachable, + }; } /// Zig wrapper for Luau lua_CompileOptions that uses the same defaults as Luau if diff --git a/src/tests.zig b/src/tests.zig index ba62ea6..44349ba 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3087,7 +3087,7 @@ test "checkNumeric and toNumeric" { })) |_| { return error.ExpectedError; } else |_| { - const string = lua.toStringEx(lua.getTop()); + const string = try lua.toString(lua.getTop()); errdefer std.log.err("expected error message to contain: {s}", .{error_msg}); errdefer std.log.err("error message: {s}", .{string}); _ = std.mem.indexOf(u8, string, error_msg) orelse return error.BadErrorMessage;