From 389ca6739b10c15062c836e67e60ba4216a6e1a6 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 5 Dec 2023 23:08:16 +0100 Subject: [PATCH 1/6] Add JSObject as public API + handle postInit functions Signed-off-by: Francis Bouvier --- src/api.zig | 1 + src/engines/v8/generate.zig | 57 +++++++++- src/engines/v8/v8.zig | 15 +++ src/reflect.zig | 201 ++++++++++++++++++++++++++++++++++++ src/run_tests.zig | 24 ++++- 5 files changed, 296 insertions(+), 2 deletions(-) diff --git a/src/api.zig b/src/api.zig index 354f0c5..4ee9f45 100644 --- a/src/api.zig +++ b/src/api.zig @@ -44,6 +44,7 @@ pub const API = Engine.API; pub const TPL = Engine.TPL; pub const JSResult = Engine.JSResult; +pub const JSObject = Engine.JSObject; pub const Callback = Engine.Callback; pub const CallbackSync = Engine.CallbackSync; pub const CallbackArg = Engine.CallbackArg; diff --git a/src/engines/v8/generate.zig b/src/engines/v8/generate.zig index 26d38a4..241085d 100644 --- a/src/engines/v8/generate.zig +++ b/src/engines/v8/generate.zig @@ -10,6 +10,7 @@ const utils = internal.utils; const public = @import("../../api.zig"); const Loop = public.Loop; +const JSObject = public.JSObject; const cbk = @import("callback.zig"); const nativeToJS = @import("types_primitives.zig").nativeToJS; @@ -167,7 +168,6 @@ fn getArg( isolate: v8.Isolate, ctx: v8.Context, ) arg.T { - _ = this; var value: arg.T = undefined; if (arg.isNative()) { @@ -192,6 +192,7 @@ fn getArg( std.mem.Allocator => alloc, *Loop => utils.loop, cbk.Func, cbk.FuncSync, cbk.Arg => unreachable, + JSObject => JSObject{ .ctx = ctx, .js_obj = this }, else => jsToNative( alloc, arg.T, @@ -530,6 +531,44 @@ fn setReturnType( return js_val; } +fn postInit( + alloc: std.mem.Allocator, + comptime T_refl: refl.Struct, + comptime all_T: []refl.Struct, + comptime func: refl.Func, + comptime argsT: type, + obj_ptr: anytype, + js_obj: v8.Object, + js_res: v8.ReturnValue, + ctx: v8.Context, + isolate: v8.Isolate, +) void { + var args: argsT = undefined; + @field(args, "0") = obj_ptr; + @field(args, "1") = JSObject{ .ctx = ctx, .js_obj = js_obj }; + const f = @field(T_refl.T, "postInit"); + const ret = comptime try refl.funcReturnType(@TypeOf(f)); + if (comptime refl.isErrorUnion(ret)) { + _ = @call(.auto, f, args) catch |err| { + return throwError( + alloc, + T_refl, + all_T, + func, + err, + js_res, + isolate, + ); + }; + } else { + _ = @call( + .auto, + f, + args, + ); + } +} + fn getNativeObject( comptime T_refl: refl.Struct, comptime all_T: []refl.Struct, @@ -647,6 +686,22 @@ fn callFunc( cbk_info.getThis(), isolate, ) catch unreachable; // TODO: internal errors + + // call postInit func + if (comptime try refl.postInitFunc(T_refl.T)) |piArgsT| { + postInit( + utils.allocator, + T_refl, + all_T, + func, + piArgsT, + &res, + cbk_info.getThis(), + cbk_info.getReturnValue(), + ctx, + isolate, + ); + } } else { // return to javascript the result diff --git a/src/engines/v8/v8.zig b/src/engines/v8/v8.zig index 2234bbb..5b0d6e3 100644 --- a/src/engines/v8/v8.zig +++ b/src/engines/v8/v8.zig @@ -18,6 +18,7 @@ pub const CallbackArg = @import("callback.zig").Arg; pub const LoadFnType = @import("generate.zig").LoadFnType; pub const loadFn = @import("generate.zig").loadFn; const setNativeObject = @import("generate.zig").setNativeObject; +const nativeToJS = @import("types_primitives.zig").nativeToJS; const valueToUtf8 = @import("types_primitives.zig").valueToUtf8; pub const API = struct { @@ -387,6 +388,20 @@ fn createJSObject( ); } +pub const JSObject = struct { + ctx: v8.Context, + js_obj: v8.Object, + + pub fn set(self: JSObject, key: []const u8, value: anytype) !void { + const isolate = self.ctx.getIsolate(); + const js_value = try nativeToJS(@TypeOf(value), value, isolate); + const js_key = v8.String.initUtf8(isolate, key); + if (!self.js_obj.setValue(self.ctx, js_key, js_value)) { + return error.SetV8Object; + } + } +}; + pub const TryCatch = struct { try_catch: *v8.TryCatch, diff --git a/src/reflect.zig b/src/reflect.zig index 6c63c97..86ff42b 100644 --- a/src/reflect.zig +++ b/src/reflect.zig @@ -9,6 +9,8 @@ const Callback = public.Callback; const CallbackSync = public.CallbackSync; const CallbackArg = public.CallbackArg; +const JSObject = public.JSObject; + const i64Num = public.i64Num; const u64Num = public.u64Num; @@ -36,6 +38,7 @@ const builtin_types = [_]type{ const internal_types = [_]type{ std.mem.Allocator, Loop, + JSObject, Callback, CallbackSync, CallbackArg, @@ -520,6 +523,11 @@ pub const Func = struct { index_offset += 1; } + // JSObject + if (args_types[i].T == JSObject) { + index_offset += 1; + } + // callback // ensure function has only 1 callback as argument // TODO: is this necessary? @@ -1213,6 +1221,15 @@ pub const Struct = struct { } } + // postInit + if (@hasDecl(T, "postInit")) { + _ = postInitFunc(T) catch { + const msg = "function 'postInit' not well formed"; + fmtErr(msg.len, msg, T); + return error.FuncPostInit; + }; + } + // check deinit // only if at least one function has an allocator argument var check_deinit = false; @@ -1407,6 +1424,181 @@ pub fn do(comptime types: anytype) Error![]Struct { } } +// New style reflect +// ----------------- + +// EqlOptions to handle how check equality is done +// if ptr, check is also done with *T +// if err, T can be wrapped in an ErrorUnion +// if opt, T can be wrapped in an Optional +// by default all those options are not allowed +const EqlOptions = struct { + ptr: bool = false, + err: bool = false, + opt: bool = false, +}; + +// assert T is equal to X +// see EqlOptions for behavior details +fn assertT(comptime T: type, comptime X: type, comptime opts: EqlOptions) !void { + if (T == X) return; + if (opts.ptr and T == *X) return; + const err = error.AssertT; + const info = @typeInfo(X); + switch (info) { + .ErrorUnion => { + if (opts.err) return try assertT(T, info.ErrorUnion.payload, opts); + return err; + }, + .Optional => { + if (opts.opt) return try assertT(T, info.Optional.child, opts); + return err; + }, + else => return err, + } +} + +// assert T is a supported container type +// currently only Struct and Union +fn assertApi(comptime T: type) !void { + const info = @typeInfo(T); + return switch (info) { + .Struct, .Union => {}, + else => error.AssertAPI, + }; +} + +// assert func is a function +fn assertFunc(comptime func: type) !void { + if (@typeInfo(func) != .Fn) return error.AssertFunc; +} + +// assert func is a method of T +// if not strict, T and *T are allowed +fn assertFuncIsMethod(comptime T: type, comptime func: type, comptime strict: bool) !void { + try assertFunc(func); + const err = error.AssertFuncIsMethod; + const info = @typeInfo(func).Fn; + if (info.params.len == 0) return err; + + const first = info.params[0].type.?; + if (strict) { + if (first != T) return err; + } else { + if (first != T and first != *T) return err; + } +} + +// assert func has the correnct number of parameters +fn assertFuncParamsNb(comptime func: type, comptime nb: u8) !void { + try assertFunc(func); + const info = @typeInfo(func).Fn; + if (info.params.len != nb) return error.AssertFuncParamsNb; +} + +// assert func has the type T as parameter +// if the index is provided, check directly the corresponding parameter +// otherwise check at least 1 parameter is of type T +fn assertFuncHasParam(comptime func: type, comptime T: type, comptime index: ?u8) !void { + try assertFunc(func); + const err = error.AssertFuncHasParam; + const info = @typeInfo(func).Fn; + + // if index is provided, check it directly + if (index) |i| { + if (info.params.len < i + 1) return err; + if (info.params[i].type.? != T) { + return err; + } + return; + } + + // otherwise check all + for (info.params) |param| { + if (param.type.? == T) { + return; + } + } + return err; +} + +// assert func returns T +// see EqlOptions for behavior details +fn assertFuncReturnT(comptime func: type, comptime T: type, comptime opts: EqlOptions) !void { + try assertFunc(func); + const ret = @typeInfo(func).Fn.return_type.?; + assertT(T, ret, opts) catch return error.AssertFuncReturnT; +} + +// createTupleT generate a tuple type +// with the members passed as fields +fn createTupleT(comptime members: []type) type { + var fields: [members.len]std.builtin.Type.StructField = undefined; + for (members, 0..) |member, i| { + fields[i] = std.builtin.Type.StructField{ + .name = try itoa(i), + .type = member, + .default_value = null, + .is_comptime = false, + .alignment = @alignOf(member), + }; + } + const s = std.builtin.Type.Struct{ + .layout = std.builtin.Type.ContainerLayout.Auto, + .fields = &fields, + .decls = &.{}, + .is_tuple = true, + }; + const t = std.builtin.Type{ .Struct = s }; + return @Type(t); +} + +// argsT generate from the func parameters +// a tuple type suitable for the @call builtin func +fn argsT(comptime func: type) type { + try assertFunc(func); + const info = @typeInfo(func).Fn; + var params: [info.params.len]type = undefined; + for (info.params, 0..) |param, i| { + params[i] = param.type.?; + } + return createTupleT(¶ms); +} + +// public functions + +// funcReturnType retrieve the return type of a func +pub fn funcReturnType(comptime func: type) !type { + std.debug.assert(@inComptime()); + try assertFunc(func); + const info = @typeInfo(func).Fn; + return info.return_type.?; +} + +// isErrorUnion check if a type is an ErrorUnion +pub fn isErrorUnion(comptime T: type) bool { + std.debug.assert(@inComptime()); + const info = @typeInfo(T); + return info == .ErrorUnion; +} + +// postInitFunc check if T has `postInit` function +// and returns the arguments tuple type expected as parameters +pub fn postInitFunc(comptime T: type) !?type { + std.debug.assert(@inComptime()); + try assertApi(T); + + const name = "postInit"; + if (!@hasDecl(T, name)) return null; + + const func = @TypeOf(@field(T, name)); + try assertFuncIsMethod(*T, func, true); + try assertFuncParamsNb(func, 2); + try assertFuncHasParam(func, JSObject, 1); + try assertFuncReturnT(func, void, .{ .err = true }); + return argsT(func); +} + // Utils funcs // ----------- @@ -1524,6 +1716,7 @@ const Error = error{ FuncVariadicNotLastOne, FuncReturnTypeVariadic, FuncErrorUnionArg, + FuncPostInit, // type errors TypeTaggedUnion, @@ -1699,6 +1892,10 @@ const TestFuncReturnTypeVariadic = struct { const TestFuncErrorUnionArg = struct { pub fn _example(_: TestFuncErrorUnionArg, _: anyerror!void) void {} }; +const TestFuncPostInit = struct { + // missing JSObject arg + pub fn postInit(_: *TestFuncPostInit) void {} +}; // types tests const TestTaggedUnion = union { @@ -1845,6 +2042,10 @@ pub fn tests() !void { .{TestFuncErrorUnionArg}, error.FuncErrorUnionArg, ); + try ensureErr( + .{TestFuncPostInit}, + error.FuncPostInit, + ); // types checks try ensureErr( diff --git a/src/run_tests.zig b/src/run_tests.zig index 39a8884..d3c7db9 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -15,6 +15,7 @@ const primitive_types = @import("tests/types_primitives_test.zig"); const native_types = @import("tests/types_native_test.zig"); const complex_types = @import("tests/types_complex_test.zig"); const multiple_types = @import("tests/types_multiple_test.zig"); +const object_types = @import("tests/types_object.zig"); const callback = @import("tests/cbk_test.zig"); test { @@ -31,8 +32,9 @@ test { const do_nat = true; // TODO: if enable alone we have "exceeded 1000 backwards branches" error const do_complex = true; const do_multi = true; + const do_obj = true; const do_cbk = true; - if (!do_proto and !do_prim and !do_nat and !do_complex and !do_multi and !do_cbk) { + if (!do_proto and !do_prim and !do_nat and !do_complex and !do_multi and !do_obj and !do_cbk) { std.debug.print("\nWARNING: No end to end tests.\n", .{}); return; } @@ -96,6 +98,17 @@ test { _ = try eng.loadEnv(&multi_arena, multiple_types.exec, multi_apis); } + // object types tests + var obj_alloc: bench.Allocator = undefined; + if (do_obj) { + tests_nb += 1; + const obj_apis = comptime object_types.generate(); // stage1: we need to comptime + obj_alloc = bench.allocator(std.testing.allocator); + var obj_arena = std.heap.ArenaAllocator.init(obj_alloc.allocator()); + defer obj_arena.deinit(); + _ = try eng.loadEnv(&obj_arena, object_types.exec, obj_apis); + } + // callback tests var cbk_alloc: bench.Allocator = undefined; if (do_cbk) { @@ -170,6 +183,15 @@ test { try t.addRow(.{ "Multiples", multi_alloc.alloc_nb, multi_alloc_size }); } + if (do_obj) { + const obj_alloc_stats = obj_alloc.stats(); + const obj_alloc_size = pretty.Measure{ + .unit = "b", + .value = obj_alloc_stats.alloc_size, + }; + try t.addRow(.{ "Objects", obj_alloc.alloc_nb, obj_alloc_size }); + } + if (do_cbk) { const cbk_alloc_stats = cbk_alloc.stats(); const cbk_alloc_size = pretty.Measure{ From 333d7be16b4c6d63d598fdb265953eaa6398f0b9 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 5 Dec 2023 23:36:26 +0100 Subject: [PATCH 2/6] Rename postInit in postJSObjectInst Rationale: this feature has nothing to do with the Type init. The idea is to get/set values on the JS object itself after its instantiation. Thoses actions are by definition dynamic and do not affect the underling Type. By consequence the corresponding Zig object will not have those values. Signed-off-by: Francis Bouvier --- src/engines/v8/generate.zig | 8 ++++---- src/reflect.zig | 26 +++++++++++++------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/engines/v8/generate.zig b/src/engines/v8/generate.zig index 241085d..30c020c 100644 --- a/src/engines/v8/generate.zig +++ b/src/engines/v8/generate.zig @@ -531,7 +531,7 @@ fn setReturnType( return js_val; } -fn postInit( +fn postJSObjectInst( alloc: std.mem.Allocator, comptime T_refl: refl.Struct, comptime all_T: []refl.Struct, @@ -546,7 +546,7 @@ fn postInit( var args: argsT = undefined; @field(args, "0") = obj_ptr; @field(args, "1") = JSObject{ .ctx = ctx, .js_obj = js_obj }; - const f = @field(T_refl.T, "postInit"); + const f = @field(T_refl.T, "postJSObjectInst"); const ret = comptime try refl.funcReturnType(@TypeOf(f)); if (comptime refl.isErrorUnion(ret)) { _ = @call(.auto, f, args) catch |err| { @@ -688,8 +688,8 @@ fn callFunc( ) catch unreachable; // TODO: internal errors // call postInit func - if (comptime try refl.postInitFunc(T_refl.T)) |piArgsT| { - postInit( + if (comptime try refl.postJSObjectInstFunc(T_refl.T)) |piArgsT| { + postJSObjectInst( utils.allocator, T_refl, all_T, diff --git a/src/reflect.zig b/src/reflect.zig index 86ff42b..6a1a0d7 100644 --- a/src/reflect.zig +++ b/src/reflect.zig @@ -1221,12 +1221,12 @@ pub const Struct = struct { } } - // postInit - if (@hasDecl(T, "postInit")) { - _ = postInitFunc(T) catch { - const msg = "function 'postInit' not well formed"; + // postJSObjectInst + if (@hasDecl(T, "postJSObjectInst")) { + _ = postJSObjectInstFunc(T) catch { + const msg = "function 'postJSObjectInst' not well formed"; fmtErr(msg.len, msg, T); - return error.FuncPostInit; + return error.FuncPostJSObjectInst; }; } @@ -1582,13 +1582,13 @@ pub fn isErrorUnion(comptime T: type) bool { return info == .ErrorUnion; } -// postInitFunc check if T has `postInit` function +// postJSObjectInstFunc check if T has `postJSObjectInst` function // and returns the arguments tuple type expected as parameters -pub fn postInitFunc(comptime T: type) !?type { +pub fn postJSObjectInstFunc(comptime T: type) !?type { std.debug.assert(@inComptime()); try assertApi(T); - const name = "postInit"; + const name = "postJSObjectInst"; if (!@hasDecl(T, name)) return null; const func = @TypeOf(@field(T, name)); @@ -1716,7 +1716,7 @@ const Error = error{ FuncVariadicNotLastOne, FuncReturnTypeVariadic, FuncErrorUnionArg, - FuncPostInit, + FuncPostJSObjectInst, // type errors TypeTaggedUnion, @@ -1892,9 +1892,9 @@ const TestFuncReturnTypeVariadic = struct { const TestFuncErrorUnionArg = struct { pub fn _example(_: TestFuncErrorUnionArg, _: anyerror!void) void {} }; -const TestFuncPostInit = struct { +const TestFuncPostJSObjectInst = struct { // missing JSObject arg - pub fn postInit(_: *TestFuncPostInit) void {} + pub fn postJSObjectInst(_: *TestFuncPostJSObjectInst) void {} }; // types tests @@ -2043,8 +2043,8 @@ pub fn tests() !void { error.FuncErrorUnionArg, ); try ensureErr( - .{TestFuncPostInit}, - error.FuncPostInit, + .{TestFuncPostJSObjectInst}, + error.FuncPostJSObjectInst, ); // types checks From fc525fdc6f9ff50bbcc4fe1654d44822d83951ba Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Tue, 5 Dec 2023 23:49:23 +0100 Subject: [PATCH 3/6] Forbid JSObject argument for constructor functions Signed-off-by: Francis Bouvier --- src/reflect.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/reflect.zig b/src/reflect.zig index 6a1a0d7..cd13f6f 100644 --- a/src/reflect.zig +++ b/src/reflect.zig @@ -525,6 +525,9 @@ pub const Func = struct { // JSObject if (args_types[i].T == JSObject) { + // JSObject arg is not allowed in a constructor function + // as the corresponding JS object has not been yet created + if (kind == .constructor) return error.FuncCstrWithJSObject; index_offset += 1; } @@ -1716,6 +1719,7 @@ const Error = error{ FuncVariadicNotLastOne, FuncReturnTypeVariadic, FuncErrorUnionArg, + FuncCstrWithJSObject, FuncPostJSObjectInst, // type errors @@ -1892,6 +1896,11 @@ const TestFuncReturnTypeVariadic = struct { const TestFuncErrorUnionArg = struct { pub fn _example(_: TestFuncErrorUnionArg, _: anyerror!void) void {} }; +const TestFuncCstrWithJSObject = struct { + pub fn constructor(_: JSObject) TestFuncCstrWithJSObject { + return .{}; + } +}; const TestFuncPostJSObjectInst = struct { // missing JSObject arg pub fn postJSObjectInst(_: *TestFuncPostJSObjectInst) void {} @@ -2042,6 +2051,10 @@ pub fn tests() !void { .{TestFuncErrorUnionArg}, error.FuncErrorUnionArg, ); + try ensureErr( + .{TestFuncCstrWithJSObject}, + error.FuncCstrWithJSObject, + ); try ensureErr( .{TestFuncPostJSObjectInst}, error.FuncPostJSObjectInst, From be960ad1b2dd49b5aad2b341cfa3d45993af618f Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 6 Dec 2023 01:56:20 +0100 Subject: [PATCH 4/6] Call postJSObjectInst when instantiate a JS object from return type Signed-off-by: Francis Bouvier --- src/engines/v8/generate.zig | 39 ++++++++++++++--- src/tests/proto_test.zig | 16 ++++--- src/tests/test_utils.zig | 18 ++++++++ src/tests/types_object.zig | 84 +++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 15 deletions(-) create mode 100644 src/tests/types_object.zig diff --git a/src/engines/v8/generate.zig b/src/engines/v8/generate.zig index 30c020c..7a5bdcf 100644 --- a/src/engines/v8/generate.zig +++ b/src/engines/v8/generate.zig @@ -396,7 +396,7 @@ pub fn setNativeObject( obj: anytype, js_obj: v8.Object, isolate: v8.Isolate, -) !void { +) !*T { // assign and bind native obj to JS obj var obj_ptr: *T = undefined; @@ -421,7 +421,7 @@ pub fn setNativeObject( // if the object is an empty struct (ie. a kind of container) // no need to keep it's reference if (T_refl.isEmpty()) { - return; + return obj_ptr; } // bind the native object pointer to JS obj @@ -437,13 +437,16 @@ pub fn setNativeObject( try refs.addObject(alloc, int_ptr.*, T_refl.index); } js_obj.setInternalField(0, ext); + return obj_ptr; } fn setReturnType( alloc: std.mem.Allocator, comptime all_T: []refl.Struct, comptime ret: refl.Type, + comptime func: refl.Func, res: anytype, + js_res: v8.ReturnValue, ctx: v8.Context, isolate: v8.Isolate, ) !v8.Value { @@ -455,7 +458,7 @@ fn setReturnType( // if null just return JS null return isolate.initNull().toValue(); } - return setReturnType(alloc, all_T, ret, res.?, ctx, isolate); + return setReturnType(alloc, all_T, ret, func, res.?, js_res, ctx, isolate); } // Union type @@ -469,7 +472,9 @@ fn setReturnType( alloc, all_T, tt, + func, @field(res, tt.name.?), + js_res, ctx, isolate, ); @@ -491,7 +496,9 @@ fn setReturnType( alloc, all_T, field, + func, @field(res, name), + js_res, ctx, isolate, ); @@ -510,14 +517,32 @@ fn setReturnType( // instantiate a JS object from template // and bind it to the native object const js_obj = gen.getTpl(index).tpl.getInstanceTemplate().initInstance(ctx); - _ = try setNativeObject( + const obj_ptr = setNativeObject( alloc, all_T[index], ret.underT(), res, js_obj, isolate, - ); + ) catch unreachable; + + // call postJSObjectInst func + const T_refl = all_T[index]; + if (comptime try refl.postJSObjectInstFunc(T_refl.T)) |piArgsT| { + postJSObjectInst( + utils.allocator, + T_refl, + all_T, + func, + piArgsT, + obj_ptr, + js_obj, + js_res, + ctx, + isolate, + ); + } + return js_obj.toValue(); } @@ -678,7 +703,7 @@ fn callFunc( if (comptime func_kind == .constructor) { // bind native object to JS object this - setNativeObject( + _ = setNativeObject( utils.allocator, T_refl, func.return_type.underT(), @@ -709,7 +734,9 @@ fn callFunc( utils.allocator, all_T, func.return_type, + func, res, + cbk_info.getReturnValue(), ctx, isolate, ) catch unreachable; // TODO: internal errors diff --git a/src/tests/proto_test.zig b/src/tests/proto_test.zig index fda84f8..4588839 100644 --- a/src/tests/proto_test.zig +++ b/src/tests/proto_test.zig @@ -254,11 +254,9 @@ pub fn exec( try js_env.start(alloc, apis); defer js_env.stop(); - const ownBase = switch (public.Env.engine()) { - .v8 => 5, - }; - const ownBaseLen = intToStr(alloc, ownBase); - defer alloc.free(ownBaseLen); + const ownBase = tests.engineOwnPropertiesDefault(); + const ownBaseStr = tests.intToStr(alloc, ownBase); + defer alloc.free(ownBaseStr); // global try js_env.attachObject(try js_env.getGlobal(), "self", null); @@ -315,15 +313,15 @@ pub fn exec( try tests.checkCases(js_env, &cases4); // static attr - const ownPersonLen = intToStr(alloc, ownBase + 2); - defer alloc.free(ownPersonLen); + const ownPersonStr = intToStr(alloc, ownBase + 2); + defer alloc.free(ownPersonStr); var cases_static = [_]tests.Case{ // basic static case .{ .src = "Person.AGE_MIN === 18", .ex = "true" }, .{ .src = "Person.NATIONALITY === 'French'", .ex = "true" }, // static attributes are own properties .{ .src = "let ownPerson = Object.getOwnPropertyNames(Person)", .ex = "undefined" }, - .{ .src = "ownPerson.length", .ex = ownPersonLen }, + .{ .src = "ownPerson.length", .ex = ownPersonStr }, // static attributes are also available on instances .{ .src = "p.AGE_MIN === 18", .ex = "true" }, .{ .src = "p.NATIONALITY === 'French'", .ex = "true" }, @@ -341,7 +339,7 @@ pub fn exec( .{ .src = "User.NATIONALITY === 'French'", .ex = "true" }, // static attributes inherited are NOT own properties .{ .src = "let ownUser = Object.getOwnPropertyNames(User)", .ex = "undefined" }, - .{ .src = "ownUser.length", .ex = ownBaseLen }, + .{ .src = "ownUser.length", .ex = ownBaseStr }, }; try tests.checkCases(js_env, &cases_proto_constructor); diff --git a/src/tests/test_utils.zig b/src/tests/test_utils.zig index bf2af01..ecf3dfc 100644 --- a/src/tests/test_utils.zig +++ b/src/tests/test_utils.zig @@ -24,6 +24,24 @@ pub fn sleep(nanoseconds: u64) void { std.os.nanosleep(s, ns); } +// result memory is owned by the caller +pub fn intToStr(alloc: std.mem.Allocator, nb: u8) []const u8 { + return std.fmt.allocPrint( + alloc, + "{d}", + .{nb}, + ) catch unreachable; +} + +// engineOwnPropertiesDefault returns the number of own properties +// by default for a current Type +// result memory is owned by the caller +pub fn engineOwnPropertiesDefault() u8 { + return switch (public.Env.engine()) { + .v8 => 5, + }; +} + var test_case: usize = 0; fn caseError(src: []const u8, exp: []const u8, res: []const u8, stack: ?[]const u8) void { diff --git a/src/tests/types_object.zig b/src/tests/types_object.zig new file mode 100644 index 0000000..3dc1917 --- /dev/null +++ b/src/tests/types_object.zig @@ -0,0 +1,84 @@ +const std = @import("std"); + +const public = @import("../api.zig"); +const tests = public.test_utils; + +pub const MyObject = struct { + val: bool, + + pub fn constructor(do_set: bool) MyObject { + return .{ .val = do_set }; + } + + pub fn postJSObjectInst(self: *MyObject, js_obj: public.JSObject) !void { + if (self.val) try js_obj.set("a", @as(u8, 1)); + } + + pub fn get_val(self: MyObject) bool { + return self.val; + } + + pub fn set_val(self: *MyObject, val: bool) void { + self.val = val; + } +}; + +pub const MyAPI = struct { + pub fn constructor() MyAPI { + return .{}; + } + + pub fn _obj(_: MyAPI, _: public.JSObject) !MyObject { + return MyObject.constructor(true); + } +}; + +// generate API, comptime +pub fn generate() []public.API { + return public.compile(.{ + MyObject, + MyAPI, + }); +} + +// exec tests +pub fn exec( + alloc: std.mem.Allocator, + js_env: *public.Env, + comptime apis: []public.API, +) !void { + + // start JS env + try js_env.start(alloc, apis); + defer js_env.stop(); + + const ownBase = tests.engineOwnPropertiesDefault(); + const ownBaseStr = tests.intToStr(alloc, ownBase); + defer alloc.free(ownBaseStr); + + var direct = [_]tests.Case{ + .{ .src = "Object.getOwnPropertyNames(MyObject).length;", .ex = ownBaseStr }, + .{ .src = "let myObj = new MyObject(true);", .ex = "undefined" }, + // check object property + .{ .src = "myObj.a", .ex = "1" }, + .{ .src = "Object.getOwnPropertyNames(myObj).length;", .ex = "1" }, + // check if setter (pointer) still works + .{ .src = "myObj.val", .ex = "true" }, + .{ .src = "myObj.val = false", .ex = "false" }, + .{ .src = "myObj.val", .ex = "false" }, + // check other object, same type, has no property + .{ .src = "let myObj2 = new MyObject(false);", .ex = "undefined" }, + .{ .src = "myObj2.a", .ex = "undefined" }, + .{ .src = "Object.getOwnPropertyNames(myObj2).length;", .ex = "0" }, + }; + try tests.checkCases(js_env, &direct); + + var indirect = [_]tests.Case{ + .{ .src = "let myAPI = new MyAPI();", .ex = "undefined" }, + .{ .src = "let myObjIndirect = myAPI.obj();", .ex = "undefined" }, + // check object property + .{ .src = "myObjIndirect.a", .ex = "1" }, + .{ .src = "Object.getOwnPropertyNames(myObjIndirect).length;", .ex = "1" }, + }; + try tests.checkCases(js_env, &indirect); +} From 0905deb3929c4ad76461829b3eaceef1899486a6 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 8 Dec 2023 12:50:35 +0100 Subject: [PATCH 5/6] Rename postJSObjectInst in postAttach Signed-off-by: Francis Bouvier --- src/engines/v8/generate.zig | 16 ++++++++-------- src/reflect.zig | 26 +++++++++++++------------- src/tests/types_object.zig | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/engines/v8/generate.zig b/src/engines/v8/generate.zig index 7a5bdcf..7f21fda 100644 --- a/src/engines/v8/generate.zig +++ b/src/engines/v8/generate.zig @@ -526,10 +526,10 @@ fn setReturnType( isolate, ) catch unreachable; - // call postJSObjectInst func + // call postAttach func const T_refl = all_T[index]; - if (comptime try refl.postJSObjectInstFunc(T_refl.T)) |piArgsT| { - postJSObjectInst( + if (comptime try refl.postAttachFunc(T_refl.T)) |piArgsT| { + postAttach( utils.allocator, T_refl, all_T, @@ -556,7 +556,7 @@ fn setReturnType( return js_val; } -fn postJSObjectInst( +fn postAttach( alloc: std.mem.Allocator, comptime T_refl: refl.Struct, comptime all_T: []refl.Struct, @@ -571,7 +571,7 @@ fn postJSObjectInst( var args: argsT = undefined; @field(args, "0") = obj_ptr; @field(args, "1") = JSObject{ .ctx = ctx, .js_obj = js_obj }; - const f = @field(T_refl.T, "postJSObjectInst"); + const f = @field(T_refl.T, "postAttach"); const ret = comptime try refl.funcReturnType(@TypeOf(f)); if (comptime refl.isErrorUnion(ret)) { _ = @call(.auto, f, args) catch |err| { @@ -712,9 +712,9 @@ fn callFunc( isolate, ) catch unreachable; // TODO: internal errors - // call postInit func - if (comptime try refl.postJSObjectInstFunc(T_refl.T)) |piArgsT| { - postJSObjectInst( + // call postAttach func + if (comptime try refl.postAttachFunc(T_refl.T)) |piArgsT| { + postAttach( utils.allocator, T_refl, all_T, diff --git a/src/reflect.zig b/src/reflect.zig index cd13f6f..7c922bc 100644 --- a/src/reflect.zig +++ b/src/reflect.zig @@ -1224,12 +1224,12 @@ pub const Struct = struct { } } - // postJSObjectInst - if (@hasDecl(T, "postJSObjectInst")) { - _ = postJSObjectInstFunc(T) catch { - const msg = "function 'postJSObjectInst' not well formed"; + // postAttach + if (@hasDecl(T, "postAttach")) { + _ = postAttachFunc(T) catch { + const msg = "function 'postAttach' not well formed"; fmtErr(msg.len, msg, T); - return error.FuncPostJSObjectInst; + return error.FuncPostAttach; }; } @@ -1585,13 +1585,13 @@ pub fn isErrorUnion(comptime T: type) bool { return info == .ErrorUnion; } -// postJSObjectInstFunc check if T has `postJSObjectInst` function +// postAttachFunc check if T has `postAttach` function // and returns the arguments tuple type expected as parameters -pub fn postJSObjectInstFunc(comptime T: type) !?type { +pub fn postAttachFunc(comptime T: type) !?type { std.debug.assert(@inComptime()); try assertApi(T); - const name = "postJSObjectInst"; + const name = "postAttach"; if (!@hasDecl(T, name)) return null; const func = @TypeOf(@field(T, name)); @@ -1720,7 +1720,7 @@ const Error = error{ FuncReturnTypeVariadic, FuncErrorUnionArg, FuncCstrWithJSObject, - FuncPostJSObjectInst, + FuncPostAttach, // type errors TypeTaggedUnion, @@ -1901,9 +1901,9 @@ const TestFuncCstrWithJSObject = struct { return .{}; } }; -const TestFuncPostJSObjectInst = struct { +const TestFuncPostAttach = struct { // missing JSObject arg - pub fn postJSObjectInst(_: *TestFuncPostJSObjectInst) void {} + pub fn postAttach(_: *TestFuncPostAttach) void {} }; // types tests @@ -2056,8 +2056,8 @@ pub fn tests() !void { error.FuncCstrWithJSObject, ); try ensureErr( - .{TestFuncPostJSObjectInst}, - error.FuncPostJSObjectInst, + .{TestFuncPostAttach}, + error.FuncPostAttach, ); // types checks diff --git a/src/tests/types_object.zig b/src/tests/types_object.zig index 3dc1917..9ed3dbc 100644 --- a/src/tests/types_object.zig +++ b/src/tests/types_object.zig @@ -10,7 +10,7 @@ pub const MyObject = struct { return .{ .val = do_set }; } - pub fn postJSObjectInst(self: *MyObject, js_obj: public.JSObject) !void { + pub fn postAttach(self: *MyObject, js_obj: public.JSObject) !void { if (self.val) try js_obj.set("a", @as(u8, 1)); } From b75e166bff7d5dadc0da1b5205b45a70403770c8 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Fri, 8 Dec 2023 12:58:05 +0100 Subject: [PATCH 6/6] Review changes Split assertFuncHasParam to assertFuncHasParamT and assertFuncParamIsT Better logic of strict parameter on assertFuncIsMethod Signed-off-by: Francis Bouvier --- src/reflect.zig | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/reflect.zig b/src/reflect.zig index 7c922bc..0fb55c6 100644 --- a/src/reflect.zig +++ b/src/reflect.zig @@ -1485,44 +1485,42 @@ fn assertFuncIsMethod(comptime T: type, comptime func: type, comptime strict: bo if (info.params.len == 0) return err; const first = info.params[0].type.?; - if (strict) { - if (first != T) return err; - } else { - if (first != T and first != *T) return err; - } + if (first == T) return; + // only non strict assertion allows *T + if (!strict and first == *T) return; + return err; } -// assert func has the correnct number of parameters +// assert func has the correct number of parameters fn assertFuncParamsNb(comptime func: type, comptime nb: u8) !void { try assertFunc(func); const info = @typeInfo(func).Fn; if (info.params.len != nb) return error.AssertFuncParamsNb; } -// assert func has the type T as parameter -// if the index is provided, check directly the corresponding parameter -// otherwise check at least 1 parameter is of type T -fn assertFuncHasParam(comptime func: type, comptime T: type, comptime index: ?u8) !void { +// assert function parameter at index is of type T +fn assertFuncParamIsT(comptime func: type, comptime T: type, comptime index: u8) !void { try assertFunc(func); const err = error.AssertFuncHasParam; const info = @typeInfo(func).Fn; - // if index is provided, check it directly - if (index) |i| { - if (info.params.len < i + 1) return err; - if (info.params[i].type.? != T) { - return err; - } - return; + if (info.params.len < index + 1) return err; + if (info.params[index].type.? != T) { + return err; } +} + +// assert function has at least 1 parameter of type T +fn assertFuncHasParamT(comptime func: type, comptime T: type) !void { + try assertFunc(func); + const info = @typeInfo(func).Fn; - // otherwise check all for (info.params) |param| { if (param.type.? == T) { return; } } - return err; + return error.AssertFuncHasParam; } // assert func returns T @@ -1597,7 +1595,7 @@ pub fn postAttachFunc(comptime T: type) !?type { const func = @TypeOf(@field(T, name)); try assertFuncIsMethod(*T, func, true); try assertFuncParamsNb(func, 2); - try assertFuncHasParam(func, JSObject, 1); + try assertFuncParamIsT(func, JSObject, 1); try assertFuncReturnT(func, void, .{ .err = true }); return argsT(func); }