Skip to content

Commit 9c2d2af

Browse files
committed
Added support for non-method functions
1 parent d355863 commit 9c2d2af

File tree

2 files changed

+88
-71
lines changed

2 files changed

+88
-71
lines changed

examples.zig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ test "Comptime only interface" {
5959
expectEqual(@as(u8, 42), iface.call("foo", .{42}));
6060
}
6161

62-
test "Owning interface with optional function" {
62+
test "Owning interface with optional function and a non-method function" {
6363
const OwningOptionalFuncTest = struct {
6464
fn run() !void {
6565
const TestOwningIface = Interface(struct {
6666
someFn: ?fn (*const SelfType, usize, usize) usize,
6767
otherFn: fn (*SelfType, usize) anyerror!void,
68+
thirdFn: fn(usize) usize,
6869
}, interface.Storage.Owning);
6970

7071
const TestStruct = struct {
@@ -81,13 +82,18 @@ test "Owning interface with optional function" {
8182
pub fn otherFn(self: *Self, new_state: usize) void {
8283
self.state = new_state;
8384
}
85+
86+
pub fn thirdFn(arg: usize) usize {
87+
return arg + 1;
88+
}
8489
};
8590

8691
var iface_instance = try TestOwningIface.init(comptime TestStruct{ .state = 0 }, std.testing.allocator);
8792
defer iface_instance.deinit();
8893

8994
try iface_instance.call("otherFn", .{100});
9095
expectEqual(@as(usize, 42), iface_instance.call("someFn", .{ 0, 42 }).?);
96+
expectEqual(@as(usize, 101), iface_instance.call("thirdFn", .{ 100 }));
9197
}
9298
};
9399

interface.zig

Lines changed: 81 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -259,63 +259,68 @@ fn getFunctionFromImpl(comptime name: []const u8, comptime FnT: type, comptime I
259259

260260
if (args.len == 0) {
261261
return null;
262+
// @TODO
262263
}
263264

264-
const arg0_type = args[0].arg_type.?;
265-
if (arg0_type != ImplT and arg0_type != *ImplT and arg0_type != *const ImplT) {
266-
return null;
267-
}
265+
if (args.len > 0) {
266+
const arg0_type = args[0].arg_type.?;
267+
const is_method = arg0_type == ImplT or arg0_type == *ImplT or arg0_type == *const ImplT;
268268

269-
const candidate_cc = @typeInfo(fn_decl.fn_type).Fn.calling_convention;
270-
switch (candidate_cc) {
271-
.Async, .Unspecified => {},
272-
else => return null,
273-
}
269+
const candidate_cc = @typeInfo(fn_decl.fn_type).Fn.calling_convention;
270+
switch (candidate_cc) {
271+
.Async, .Unspecified => {},
272+
else => return null,
273+
}
274274

275-
const Return = @typeInfo(FnT).Fn.return_type orelse noreturn;
276-
const CurrSelfType = @typeInfo(FnT).Fn.args[0].arg_type.?;
275+
const Return = @typeInfo(FnT).Fn.return_type orelse noreturn;
276+
const CurrSelfType = @typeInfo(FnT).Fn.args[0].arg_type.?;
277277

278-
const call_type: GenCallType = switch (our_cc) {
279-
.Async => if (candidate_cc == .Async) .BothAsync else .AsyncCallsBlocking,
280-
.Unspecified => if (candidate_cc == .Unspecified) .BothBlocking else .BlockingCallsAsync,
281-
else => unreachable,
282-
};
278+
const call_type: GenCallType = switch (our_cc) {
279+
.Async => if (candidate_cc == .Async) .BothAsync else .AsyncCallsBlocking,
280+
.Unspecified => if (candidate_cc == .Unspecified) .BothBlocking else .BlockingCallsAsync,
281+
else => unreachable,
282+
};
283283

284-
// TODO: Make this less hacky somehow?
285-
// We need some new feature to do so unfortunately.
286-
return switch (args.len) {
287-
1 => struct {
288-
fn impl(self_ptr: CurrSelfType) callconv(our_cc) Return {
289-
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{} });
290-
}
291-
}.impl,
292-
2 => struct {
293-
fn impl(self_ptr: CurrSelfType, arg: args[1].arg_type.?) callconv(our_cc) Return {
294-
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{arg} });
295-
}
296-
}.impl,
297-
3 => struct {
298-
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?) callconv(our_cc) Return {
299-
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2 } });
300-
}
301-
}.impl,
302-
4 => struct {
303-
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?) callconv(our_cc) Return {
304-
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3 } });
305-
}
306-
}.impl,
307-
5 => struct {
308-
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?, arg4: args[4].arg_type.?) callconv(our_cc) Return {
309-
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3, arg4 } });
310-
}
311-
}.impl,
312-
6 => struct {
313-
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?, arg4: args[4].arg_type.?, arg5: args[5].arg_type.?) callconv(our_cc) Return {
314-
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3, arg4, arg5 } });
315-
}
316-
}.impl,
317-
else => @compileError("Unsupported number of arguments, please provide a manually written vtable."),
318-
};
284+
if (!is_method) {
285+
return @field(ImplT, name);
286+
}
287+
288+
// TODO: Make this less hacky somehow?
289+
// We need some new feature to do so unfortunately.
290+
return switch (args.len) {
291+
1 => struct {
292+
fn impl(self_ptr: CurrSelfType) callconv(our_cc) Return {
293+
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{} });
294+
}
295+
}.impl,
296+
2 => struct {
297+
fn impl(self_ptr: CurrSelfType, arg: args[1].arg_type.?) callconv(our_cc) Return {
298+
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{arg} });
299+
}
300+
}.impl,
301+
3 => struct {
302+
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?) callconv(our_cc) Return {
303+
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2 } });
304+
}
305+
}.impl,
306+
4 => struct {
307+
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?) callconv(our_cc) Return {
308+
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3 } });
309+
}
310+
}.impl,
311+
5 => struct {
312+
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?, arg4: args[4].arg_type.?) callconv(our_cc) Return {
313+
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3, arg4 } });
314+
}
315+
}.impl,
316+
6 => struct {
317+
fn impl(self_ptr: CurrSelfType, arg1: args[1].arg_type.?, arg2: args[2].arg_type.?, arg3: args[3].arg_type.?, arg4: args[4].arg_type.?, arg5: args[5].arg_type.?) callconv(our_cc) Return {
318+
return @call(.{ .modifier = .always_inline }, makeCall, .{ name, CurrSelfType, Return, ImplT, call_type, self_ptr, .{ arg1, arg2, arg3, arg4, arg5 } });
319+
}
320+
}.impl,
321+
else => @compileError("Unsupported number of arguments, please provide a manually written vtable."),
322+
};
323+
}
319324
},
320325
else => return null,
321326
}
@@ -384,23 +389,19 @@ fn checkVtableType(comptime VTableT: type) void {
384389
.Unspecified, .Async => {},
385390
else => @compileError("Virtual function's '" ++ field.name ++ "' calling convention is not default or async."),
386391
}
387-
388-
if (type_info.Fn.args.len == 0) {
389-
@compileError("Virtual function '" ++ field.name ++ "' must have at least one argument.");
390-
}
391-
392-
const arg_type = type_info.Fn.args[0].arg_type.?;
393-
if (arg_type != *SelfType and arg_type != *const SelfType) {
394-
@compileError("Virtual function's '" ++ field.name ++ "' first argument must be *SelfType or *const SelfType");
395-
}
396392
}
397393
}
398394

399-
fn vtableHasMethod(comptime VTableT: type, comptime name: []const u8, is_optional: *bool, is_async: *bool) bool {
395+
fn vtableHasMethod(comptime VTableT: type, comptime name: []const u8, is_optional: *bool, is_async: *bool, is_method: *bool) bool {
400396
for (std.meta.fields(VTableT)) |field| {
401397
if (std.mem.eql(u8, name, field.name)) {
402398
is_optional.* = trait.is(.Optional)(field.field_type);
403-
is_async.* = @typeInfo(if (is_optional.*) std.meta.Child(field.field_type) else field.field_type).Fn.calling_convention == .Async;
399+
const fn_typeinfo = @typeInfo(if (is_optional.*) std.meta.Child(field.field_type) else field.field_type).Fn;
400+
is_async.* = fn_typeinfo.calling_convention == .Async;
401+
is_method.* = fn_typeinfo.args.len > 0 and blk: {
402+
const first_arg_type = fn_typeinfo.args[0].arg_type.?;
403+
break :blk first_arg_type == *SelfType or first_arg_type == *const SelfType;
404+
};
404405
return true;
405406
}
406407
}
@@ -450,29 +451,39 @@ pub fn Interface(comptime VTableT: type, comptime StorageT: type) type {
450451
pub fn initWithVTable(vtable_ptr: *const VTableT, args: anytype) !Self {
451452
return .{
452453
.vtable_ptr = vtable_ptr,
453-
.storage = try StorageT.init(args),
454+
.storage = try init(args),
454455
};
455456
}
456457

457458
pub fn call(self: anytype, comptime name: []const u8, args: anytype) VTableReturnType(VTableT, name) {
458459
comptime var is_optional = true;
459460
comptime var is_async = true;
460-
comptime assert(vtableHasMethod(VTableT, name, &is_optional, &is_async));
461+
comptime var is_method = true;
462+
comptime assert(vtableHasMethod(VTableT, name, &is_optional, &is_async, &is_method));
461463

462464
const fn_ptr = if (is_optional) blk: {
463465
const val = @field(self.vtable_ptr, name);
464466
if (val) |v| break :blk v;
465467
return null;
466468
} else @field(self.vtable_ptr, name);
467469

468-
const self_ptr = self.storage.getSelfPtr();
469-
const new_args = .{self_ptr};
470+
if (is_method) {
471+
const self_ptr = self.storage.getSelfPtr();
472+
const new_args = .{self_ptr};
470473

471-
if (!is_async) {
472-
return @call(.{}, fn_ptr, new_args ++ args);
474+
if (!is_async) {
475+
return @call(.{}, fn_ptr, new_args ++ args);
476+
} else {
477+
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
478+
return await @asyncCall(&stack_frame, {}, fn_ptr, new_args ++ args);
479+
}
473480
} else {
474-
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
475-
return await @asyncCall(&stack_frame, {}, fn_ptr, new_args ++ args);
481+
if (!is_async) {
482+
return @call(.{}, fn_ptr, args);
483+
} else {
484+
var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
485+
return await @asyncCall(&stack_frame, {}, fn_ptr, args);
486+
}
476487
}
477488
}
478489

0 commit comments

Comments
 (0)