Skip to content

Commit b301a77

Browse files
committed
Improve Table API ergonomics by removing optional unwrapping
Replace optional return types with error handling for Table.get() and Table.getRaw(). This eliminates the need for repetitive optional unwrapping while maintaining type safety. - Table.get() now returns T instead of ?T, throws Error.KeyNotFound when key missing - Table.getRaw() now returns T instead of ?T, throws Error.KeyNotFound when index missing - Remove getRequired() method as get() now provides the same behavior - Add Error.KeyNotFound for missing keys or conversion failures - Update all tests and documentation examples to use simplified API - Update Debug.zig tests to work with new non-optional Function references
1 parent cda187c commit b301a77

File tree

3 files changed

+107
-116
lines changed

3 files changed

+107
-116
lines changed

src/Debug.zig

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -765,8 +765,8 @@ test "debugTrace shows function names in call stack" {
765765

766766
// Get the innerFunc and set a breakpoint
767767
const func = try lua.globals().get("innerFunc", Lua.Function);
768-
defer func.?.deinit();
769-
_ = try func.?.setBreakpoint(2, true); // Line 2: return 42
768+
defer func.deinit();
769+
_ = try func.setBreakpoint(2, true); // Line 2: return 42
770770

771771
// Call the nested functions - this should hit the breakpoint
772772
const result = try lua.eval("return outerFunc()", .{}, i32);
@@ -819,16 +819,15 @@ test "breakpoint and single step debugging with callbacks" {
819819

820820
// Get function reference
821821
const func = try lua.globals().get("test_func", Lua.Function);
822-
try expect(func != null);
823-
defer func.?.deinit();
822+
defer func.deinit();
824823

825-
const breakpoint_line = try func.?.setBreakpoint(4, true);
824+
const breakpoint_line = try func.setBreakpoint(4, true);
826825
try expectEqual(breakpoint_line, 5);
827826

828827
lua.debug().setSingleStep(true);
829828
lua.setCallbacks(&callbacks);
830829

831-
const result = try func.?.call(.{}, i32);
830+
const result = try func.call(.{}, i32);
832831

833832
try expectEqual(result.ok, 60);
834833
try expectEqual(callbacks.break_hits, 1);
@@ -864,17 +863,17 @@ test "coroutine debug break" {
864863
, .{}, void);
865864

866865
const func = try thread.globals().get("test_func", Lua.Function);
867-
defer func.?.deinit();
866+
defer func.deinit();
868867

869-
_ = try func.?.setBreakpoint(2, true);
868+
_ = try func.setBreakpoint(2, true);
870869

871-
const first_result = try func.?.call(.{}, i32);
870+
const first_result = try func.call(.{}, i32);
872871
try expectEqual(first_result, .debugBreak);
873872

874873
try expectEqual(thread.status(), .normal);
875874

876875
// Call function again - should complete and return result
877-
const second_result = try func.?.call(.{}, i32);
876+
const second_result = try func.call(.{}, i32);
878877
try expectEqual(second_result.ok, 42);
879878

880879
// Assert debugbreak callback was called exactly twice
@@ -929,11 +928,11 @@ test "getInfo and stackDepth in debug breakpoint" {
929928

930929
// Get the function and set a breakpoint
931930
const func = try lua.globals().get("testFunction", Lua.Function);
932-
defer func.?.deinit();
933-
_ = try func.?.setBreakpoint(3, true); // Line 3: return x * 2
931+
defer func.deinit();
932+
_ = try func.setBreakpoint(3, true); // Line 3: return x * 2
934933

935934
// Call the function - this should hit the breakpoint
936-
const result = try func.?.call(.{ 5, 10 }, i32);
935+
const result = try func.call(.{ 5, 10 }, i32);
937936
try expectEqual(result.ok.?, 30);
938937

939938
// Verify breakpoint was hit
@@ -955,7 +954,7 @@ test "getInfo outside of hook" {
955954
_ = try lua.eval(code, .{}, void);
956955

957956
// Get the function and execute it
958-
const func = (try lua.globals().get("testFunction", Lua.Function)).?;
957+
const func = try lua.globals().get("testFunction", Lua.Function);
959958
defer func.deinit();
960959

961960
const result = try func.call(.{ 5, 10 }, i32);
@@ -1036,11 +1035,11 @@ test "getArg in debug breakpoint" {
10361035

10371036
// Get the function and set a breakpoint
10381037
const func = try lua.globals().get("testArguments", Lua.Function);
1039-
defer func.?.deinit();
1040-
_ = try func.?.setBreakpoint(3, true); // Line 3: return sum
1038+
defer func.deinit();
1039+
_ = try func.setBreakpoint(3, true); // Line 3: return sum
10411040

10421041
// Call the function with known arguments - this should hit the breakpoint
1043-
const result = try func.?.call(.{ 100, 200 }, i32);
1042+
const result = try func.call(.{ 100, 200 }, i32);
10441043
try expectEqual(result.ok.?, 300);
10451044

10461045
// Verify breakpoint was hit and arguments were tested
@@ -1132,10 +1131,10 @@ test "getLocal and setLocal in debug breakpoint" {
11321131
}
11331132

11341133
const func = try lua.globals().get("testLocals", Lua.Function);
1135-
defer func.?.deinit();
1136-
_ = try func.?.setBreakpoint(5, true); // Line 5: return sum
1134+
defer func.deinit();
1135+
_ = try func.setBreakpoint(5, true); // Line 5: return sum
11371136

1138-
const call_result = try func.?.call(.{ 100, 200 }, i32);
1137+
const call_result = try func.call(.{ 100, 200 }, i32);
11391138
try expectEqual(call_result.ok.?, 400);
11401139

11411140
try expect(LocalVariableTester.breakpoint_hit);
@@ -1227,13 +1226,13 @@ test "getUpvalue and setUpvalue in debug breakpoint" {
12271226
}
12281227

12291228
const func = try lua.globals().get("innerFunction", Lua.Function);
1230-
defer func.?.deinit();
1229+
defer func.deinit();
12311230

12321231
// Try line 3 (function start is line 2, line 3 is return)
1233-
const actual_line = try func.?.setBreakpoint(3, true);
1232+
const actual_line = try func.setBreakpoint(3, true);
12341233
try expectEqual(actual_line, 3);
12351234

1236-
const call_result = try func.?.call(.{}, i32);
1235+
const call_result = try func.call(.{}, i32);
12371236
try expectEqual(call_result.ok.?, 1998); // 999 * 2 (modified upvalue)
12381237

12391238
try expect(UpvalueTester.breakpoint_hit);
@@ -1270,35 +1269,35 @@ test "high-level getUpvalue and setUpvalue API" {
12701269
}
12711270

12721271
const func = try lua.globals().get("closure", Lua.Function);
1273-
defer func.?.deinit();
1272+
defer func.deinit();
12741273

12751274
const debug = lua.debug();
12761275

12771276
// Test getting upvalue using high-level API
1278-
const upval1 = debug.getUpvalue(func.?, 1, i32);
1277+
const upval1 = debug.getUpvalue(func, 1, i32);
12791278
try expect(upval1 != null);
12801279
try expectEqual(upval1.?.value, 42);
12811280
try expect(std.mem.eql(u8, upval1.?.name, "shared_value"));
12821281

12831282
// Test setting upvalue using high-level API
1284-
const set_name = debug.setUpvalue(func.?, 1, i32, 100);
1283+
const set_name = debug.setUpvalue(func, 1, i32, 100);
12851284
try expect(set_name != null);
12861285
try expect(std.mem.eql(u8, set_name.?, "shared_value"));
12871286

12881287
// Verify the upvalue was changed
1289-
const upval2 = debug.getUpvalue(func.?, 1, i32);
1288+
const upval2 = debug.getUpvalue(func, 1, i32);
12901289
try expect(upval2 != null);
12911290
try expectEqual(upval2.?.value, 100);
12921291

12931292
// Test the function returns the modified upvalue
1294-
const call_result = try func.?.call(.{}, i32);
1293+
const call_result = try func.call(.{}, i32);
12951294
try expectEqual(call_result.ok.?, 100);
12961295

12971296
// Test getting non-existent upvalue
1298-
const upval_invalid = debug.getUpvalue(func.?, 10, i32);
1297+
const upval_invalid = debug.getUpvalue(func, 10, i32);
12991298
try expect(upval_invalid == null);
13001299

13011300
// Test setting non-existent upvalue
1302-
const set_invalid = debug.setUpvalue(func.?, 10, i32, 42);
1301+
const set_invalid = debug.setUpvalue(func, 10, i32, 42);
13031302
try expect(set_invalid == null);
13041303
}

src/Lua.zig

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ pub const Error = error{
6060
Runtime,
6161
/// Breakpoint could not be set at the specified line.
6262
InvalidBreakpoint,
63+
/// Key not found in table or value could not be converted to requested type.
64+
KeyNotFound,
6365
};
6466

6567
/// Assert handler function type for Luau VM assertions.
@@ -582,26 +584,25 @@ pub const Table = struct {
582584
/// Directly retrieves `table[index]` without invoking metamethods.
583585
/// This is faster than `get()` but doesn't respect custom table behavior.
584586
///
585-
/// Returns `null` if the index doesn't exist or the value cannot be converted to type `T`.
586-
///
587587
/// Examples:
588588
/// ```zig
589589
/// const value = try table.getRaw(1, i32); // Get table[1] as i32
590590
/// const text = try table.getRaw(5, []u8); // Get table[5] as string
591591
/// const flag = try table.getRaw(-1, bool); // Get table[-1] as bool
592592
/// ```
593593
///
594-
/// Returns: `?T` - The converted value, or `null` if not found or conversion failed
595-
/// Errors: `Error.OutOfMemory` if stack allocation fails
596-
pub fn getRaw(self: Table, index: i32, comptime T: type) !?T {
594+
/// Returns: `T` - The converted value
595+
/// Errors: `Error.OutOfMemory` if stack allocation fails, `Error.KeyNotFound` if index doesn't exist or conversion failed
596+
pub fn getRaw(self: Table, index: i32, comptime T: type) !T {
597597
try self.ref.lua.checkStack(2);
598598

599599
stack.push(self.state(), self.ref); // Push table ref
600600
_ = self.state().rawGetI(-1, index); // Push value of t[i] onto stack.
601601

602602
defer self.state().pop(1); // Pop table
603603

604-
return stack.pop(self.ref.lua, T);
604+
const value = stack.pop(self.ref.lua, T);
605+
return value orelse error.KeyNotFound;
605606
}
606607

607608
/// Sets a table element by key with full Lua semantics (invokes __newindex metamethod).
@@ -747,8 +748,6 @@ pub const Table = struct {
747748
/// - Lua nil → Optional types (`?T`) as `null`
748749
/// - Any valid value → Optional types (`?T`) as wrapped value
749750
///
750-
/// Returns `null` if the key doesn't exist or the value cannot be converted to type `T`.
751-
///
752751
/// Note: String conversion is not supported via `get` due to Lua's garbage collection.
753752
/// For safe string handling, use Lua code with `eval()` or the low-level State API.
754753
///
@@ -761,7 +760,7 @@ pub const Table = struct {
761760
/// const pos = try table.get("pos", @Vector(3, f32)); // Get vector value
762761
///
763762
/// // Optional types (handle missing values gracefully)
764-
/// const maybe_value = try table.get("missing", ?i32); // null if missing
763+
/// const maybe_value = table.get("missing", ?i32) catch null; // null if missing
765764
/// const nullable = try table.get("nil_field", ?i32); // null if nil
766765
///
767766
/// // Different key types
@@ -775,9 +774,9 @@ pub const Table = struct {
775774
/// // The tuple becomes a nested table accessible by index
776775
/// ```
777776
///
778-
/// Returns: `?T` - The converted value, or `null` if not found or conversion failed
779-
/// Errors: `Error.OutOfMemory` if stack allocation fails
780-
pub fn get(self: Table, key: anytype, comptime T: type) !?T {
777+
/// Returns: `T` - The converted value
778+
/// Errors: `Error.OutOfMemory` if stack allocation fails, `Error.KeyNotFound` if key doesn't exist or conversion failed
779+
pub fn get(self: Table, key: anytype, comptime T: type) !T {
781780
try self.ref.lua.checkStack(2);
782781

783782
stack.push(self.state(), self.ref); // Push table ref
@@ -786,7 +785,8 @@ pub const Table = struct {
786785
_ = self.state().getTable(-2); // Pop key and push "table[key]" onto stack
787786
defer self.state().pop(1); // Pop table
788787

789-
return stack.pop(self.ref.lua, T);
788+
const value = stack.pop(self.ref.lua, T);
789+
return value orelse error.KeyNotFound;
790790
}
791791

792792
/// Calls a function stored in the table.
@@ -1624,7 +1624,7 @@ pub const Function = struct {
16241624
///
16251625
/// // Coroutine example - function can yield
16261626
/// const thread = lua.createThread();
1627-
/// const func = try thread.globals().get("coroutine_func", Function).?;
1627+
/// const func = try thread.globals().get("coroutine_func", Function);
16281628
/// defer func.deinit();
16291629
///
16301630
/// const result1 = try func.call(.{}, i32); // Start coroutine, may yield
@@ -1665,13 +1665,13 @@ pub const Function = struct {
16651665
/// _ = try lua.eval("function fibonacci(n) return n < 2 and n or fibonacci(n-1) + fibonacci(n-2) end", .{}, void);
16661666
/// const globals = lua.globals();
16671667
/// const fib = try globals.get("fibonacci", Lua.Function);
1668-
/// defer fib.?.deinit();
1668+
/// defer fib.deinit();
16691669
///
16701670
/// // Compile for better performance
1671-
/// fib.?.compile();
1671+
/// fib.compile();
16721672
///
16731673
/// // Function calls now use compiled native code
1674-
/// const result = try fib.?.call(.{10}, i32);
1674+
/// const result = try fib.call(.{10}, i32);
16751675
/// }
16761676
/// ```
16771677
pub fn compile(self: @This()) void {
@@ -1690,9 +1690,9 @@ pub const Function = struct {
16901690
/// Example:
16911691
/// ```zig
16921692
/// const func = try lua.globals().get("myFunc", Lua.Function);
1693-
/// defer func.?.deinit();
1693+
/// defer func.deinit();
16941694
///
1695-
/// const cloned = try func.?.clone();
1695+
/// const cloned = try func.clone();
16961696
/// defer cloned.deinit();
16971697
/// ```
16981698
///
@@ -2521,8 +2521,8 @@ test "table ops" {
25212521
try expectEq(try table.get("flag", bool), false);
25222522

25232523
// Test non-existent keys
2524-
try expectEq(try table.getRaw(999, i32), null);
2525-
try expectEq(try table.get("missing", i32), null);
2524+
try expectError(Error.KeyNotFound, table.getRaw(999, i32));
2525+
try expectError(Error.KeyNotFound, table.get("missing", i32));
25262526

25272527
// Test pushing table to stack
25282528
try table.set("test", 42);

0 commit comments

Comments
 (0)