diff --git a/src/runtime/js.zig b/src/runtime/js.zig index caf9f2ec..5ef243f4 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1,16 +1,20 @@ -// Copyright 2023-2024 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Francis Bouvier +// Pierre Tachoire // -// http://www.apache.org/licenses/LICENSE-2.0 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const builtin = @import("builtin"); @@ -548,12 +552,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type { } fn generateIndexer(_: *Self, comptime Struct: type, template_proto: v8.ObjectTemplate) void { - var has_one = false; - var configuration = v8.IndexedPropertyHandlerConfiguration{}; - - if (@hasDecl(Struct, "indexed_get")) { - has_one = true; - configuration.getter = struct { + if (@hasDecl(Struct, "indexed_get") == false) { + return; + } + const configuration = v8.IndexedPropertyHandlerConfiguration{ + .getter = struct { fn callback(idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) void { const info = v8.PropertyCallbackInfo.initFromV8(raw_info); var caller = Caller(Self).init(info); @@ -564,44 +567,25 @@ pub fn Env(comptime S: type, comptime types: anytype) type { caller.handleError(named_function, err, info); }; } - }.callback; - } + }.callback, + }; - if (@hasDecl(Struct, "indexed_set")) { - has_one = true; - configuration.setter = struct { - fn callback(idx: u32, raw_value: ?*const v8.C_Value, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) void { - const info = v8.PropertyCallbackInfo.initFromV8(raw_info); - var caller = Caller(Self).init(info); - defer caller.deinit(); - - const js_value = v8.Value{ .handle = raw_value.? }; - const named_function = NamedFunction(Struct, Struct.indexed_set, "indexed_set"){}; - caller.setIndex(named_function, idx, js_value, info) catch |err| { - caller.handleError(named_function, err, info); - }; - } - }.callback; - } - - if (has_one) { - template_proto.setIndexedProperty(configuration, null); - } + // If you're trying to implement setter, read: + // https://groups.google.com/g/v8-users/c/8tahYBsHpgY/m/IteS7Wn2AAAJ + // The issue I had was + // (a) where to attache it: does it go ont he instance_template + // instead of the prototype? + // (b) defining the getter or query to respond with the + // PropertyAttribute to indicate if the property can be set + template_proto.setIndexedProperty(configuration, null); } fn generateNamedIndexer(_: *Self, comptime Struct: type, template_proto: v8.ObjectTemplate) void { - var has_one = false; - var configuration = v8.NamedPropertyHandlerConfiguration{ - // This is really cool. Without this, we'd intercept _all_ properties - // even those explicitly set. So, node.length for example would get routed - // to our `named_get`, rather than a `get_length`. This might be - // useful if we run into a type that we can't model properly in Zig. - .flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking, - }; - - if (@hasDecl(Struct, "named_get")) { - has_one = true; - configuration.getter = struct { + if (@hasDecl(Struct, "named_get") == false) { + return; + } + const configuration = v8.NamedPropertyHandlerConfiguration{ + .getter = struct { fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) void { const info = v8.PropertyCallbackInfo.initFromV8(raw_info); var caller = Caller(Self).init(info); @@ -612,29 +596,23 @@ pub fn Env(comptime S: type, comptime types: anytype) type { caller.handleError(named_function, err, info); }; } - }.callback; - } + }.callback, - if (@hasDecl(Struct, "named_set")) { - has_one = true; - configuration.setter = struct { - fn callback(c_name: ?*const v8.C_Name, raw_value: ?*const v8.C_Value, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) void { - const info = v8.PropertyCallbackInfo.initFromV8(raw_info); - var caller = Caller(Self).init(info); - defer caller.deinit(); + // This is really cool. Without this, we'd intercept _all_ properties + // even those explicitly set. So, node.length for example would get routed + // to our `named_get`, rather than a `get_length`. This might be + // useful if we run into a type that we can't model properly in Zig. + .flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking, + }; - const js_value = v8.Value{ .handle = raw_value.? }; - const named_function = NamedFunction(Struct, Struct.named_set, "named_set"){}; - caller.setNamedIndex(named_function, .{ .handle = c_name.? }, js_value, info) catch |err| { - caller.handleError(named_function, err, info); - }; - } - }.callback; - } - - if (has_one) { - template_proto.setNamedProperty(configuration, null); - } + // If you're trying to implement setter, read: + // https://groups.google.com/g/v8-users/c/8tahYBsHpgY/m/IteS7Wn2AAAJ + // The issue I had was + // (a) where to attache it: does it go ont he instance_template + // instead of the prototype? + // (b) defining the getter or query to respond with the + // PropertyAttribute to indicate if the property can be set + template_proto.setNamedProperty(configuration, null); } // Turns a Zig value into a JS one. @@ -1508,41 +1486,6 @@ fn Caller(comptime E: type) type { } } - fn setIndex(self: *Self, comptime named_function: anytype, idx: u32, js_value: v8.Value, info: v8.PropertyCallbackInfo) !void { - const S = named_function.S; - comptime assertSelfReceiver(named_function); - - const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis()); - - const IndexedSet = @TypeOf(named_function.func); - var args: ParamterTypes(IndexedSet) = undefined; - const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields; - switch (arg_fields.len) { - 0, 1, 2 => @compileError(named_function.full_name ++ " must take at least a u32 parameter and a value"), - 3, 4 => { - @field(args, "0") = zig_instance; - @field(args, "1") = idx; - @field(args, "2") = try self.jsValueToZig(named_function, arg_fields[2].type, js_value); - if (comptime arg_fields.len == 4) { - comptime assertIsStateArg(named_function, 3); - @field(args, "3") = self.executor.state; - } - }, - else => @compileError(named_function.full_name ++ " has too many parmaters"), - } - - switch (@typeInfo(@typeInfo(IndexedSet).@"fn".return_type.?)) { - .error_union => |eu| { - if (eu.payload == void) { - return @call(.auto, S.indexed_set, args); - } - }, - .void => return @call(.auto, S.indexed_set, args), - else => {}, - } - @compileError(named_function.full_name ++ " cannot have a return type"); - } - fn getNamedIndex(self: *Self, comptime named_function: anytype, name: v8.Name, info: v8.PropertyCallbackInfo) !void { const S = named_function.S; const NamedGet = @TypeOf(named_function.func); @@ -1579,41 +1522,6 @@ fn Caller(comptime E: type) type { } } - fn setNamedIndex(self: *Self, comptime named_function: anytype, name: v8.Name, js_value: v8.Value, info: v8.PropertyCallbackInfo) !void { - const S = named_function.S; - comptime assertSelfReceiver(named_function); - - const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis()); - - const IndexedSet = @TypeOf(named_function.func); - var args: ParamterTypes(IndexedSet) = undefined; - const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields; - switch (arg_fields.len) { - 0, 1, 2 => @compileError(named_function.full_name ++ " must take at least an u32 parameter and a value"), - 3, 4 => { - @field(args, "0") = zig_instance; - @field(args, "1") = try self.nameToString(name); - @field(args, "2") = try self.jsValueToZig(named_function, arg_fields[2].type, js_value); - if (comptime arg_fields.len == 4) { - comptime assertIsStateArg(named_function, 3); - @field(args, "3") = self.executor.state; - } - }, - else => @compileError(named_function.full_name ++ " has too many parmaters"), - } - - switch (@typeInfo(@typeInfo(IndexedSet).@"fn".return_type.?)) { - .error_union => |eu| { - if (eu.payload == void) { - return @call(.auto, S.named_set, args); - } - }, - .void => return @call(.auto, S.named_set, args), - else => {}, - } - @compileError(named_function.full_name ++ " cannot have a return type"); - } - fn nameToString(self: *Self, name: v8.Name) ![]const u8 { return valueToString(self.call_allocator, .{ .handle = name.handle }, self.isolate, self.context); } @@ -2295,4 +2203,5 @@ fn getTaggedAnyOpaque(value: v8.Value) ?*TaggedAnyOpaque { test { std.testing.refAllDecls(@import("test_primitive_types.zig")); std.testing.refAllDecls(@import("test_complex_types.zig")); + std.testing.refAllDecls(@import("test_object_types.zig")); } diff --git a/src/runtime/loop.zig b/src/runtime/loop.zig index e9ce9c52..db23d10b 100644 --- a/src/runtime/loop.zig +++ b/src/runtime/loop.zig @@ -1,16 +1,20 @@ -// Copyright 2023-2024 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Francis Bouvier +// Pierre Tachoire // -// http://www.apache.org/licenses/LICENSE-2.0 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const MemoryPool = std.heap.MemoryPool; diff --git a/src/runtime/test_complex_types.zig b/src/runtime/test_complex_types.zig index 1173c027..05744e4c 100644 --- a/src/runtime/test_complex_types.zig +++ b/src/runtime/test_complex_types.zig @@ -1,16 +1,20 @@ -// Copyright 2023-2024 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Francis Bouvier +// Pierre Tachoire // -// http://www.apache.org/licenses/LICENSE-2.0 +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const Allocator = std.mem.Allocator; diff --git a/src/runtime/test_object_types.zig b/src/runtime/test_object_types.zig new file mode 100644 index 00000000..7556e9dd --- /dev/null +++ b/src/runtime/test_object_types.zig @@ -0,0 +1,121 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub const Other = struct { + val: u8, + + fn init(val: u8) Other { + return .{ .val = val }; + } + + pub fn _val(self: *const Other) u8 { + return self.val; + } +}; + +pub const OtherUnion = union(enum) { + Other: Other, + Bool: bool, +}; + +pub const MyObject = struct { + val: bool, + + pub fn constructor(do_set: bool) MyObject { + return .{.val = do_set,}; + } + + pub fn named_get(_: *const MyObject, name: []const u8, has_value: *bool) ?OtherUnion { + if (std.mem.eql(u8, name, "a")) { + has_value.* = true; + return .{.Other = .{.val = 4}}; + } + if (std.mem.eql(u8, name, "c")) { + has_value.* = true; + return .{.Bool = true}; + } + has_value.* = false; + return null; + } + + pub fn get_val(self: *const 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(_: *const MyAPI) !MyObject { + return MyObject.constructor(true); + } +}; + +const State = struct { + arena: Allocator, +}; + +const testing = @import("testing.zig"); +test "JS: object types" { + var arena = std.heap.ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + + var runner = try testing.Runner(State, void, .{ + Other, + MyObject, + MyAPI, + }).init(.{ .arena = arena.allocator() }, {}); + + defer runner.deinit(); + + // v8 has 5 default "own" properties + const own_base = "5"; + + try runner.testCases(&.{ + .{ "Object.getOwnPropertyNames(MyObject).length;", own_base }, + .{ "let myObj = new MyObject(true);", "undefined" }, + // check object property + .{ "myObj.a.val()", "4" }, + .{ "myObj.b", "undefined" }, + .{ "Object.getOwnPropertyNames(myObj).length;", "0" }, + + // check if setter (pointer) still works + .{ "myObj.val", "true" }, + .{ "myObj.val = false", "false" }, + .{ "myObj.val", "false" }, + + .{ "let myObj2 = new MyObject(false);", "undefined" }, + .{ "myObj2.c", "true" }, + }, .{}); + + try runner.testCases(&.{ + .{ "let myAPI = new MyAPI();", "undefined" }, + .{ "let myObjIndirect = myAPI.obj();", "undefined" }, + // check object property + .{ "myObjIndirect.a.val()", "4" }, + }, .{}); +} diff --git a/src/runtime/test_primitive_types.zig b/src/runtime/test_primitive_types.zig index 178a1d79..022a8c91 100644 --- a/src/runtime/test_primitive_types.zig +++ b/src/runtime/test_primitive_types.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + const std = @import("std"); // TODO: use functions instead of "fake" struct once we handle function API generation diff --git a/src/runtime/testing.zig b/src/runtime/testing.zig index c19632ff..2a09b2e8 100644 --- a/src/runtime/testing.zig +++ b/src/runtime/testing.zig @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + const std = @import("std"); const js = @import("js.zig"); const generate = @import("generate.zig");