Add more tests

Remove index and named setters, since they aren't working, and they aren't
currently needed.
This commit is contained in:
Karl Seguin
2025-04-15 20:37:59 +08:00
parent 180359e148
commit ea6f8ce4d9
6 changed files with 235 additions and 161 deletions

View File

@@ -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 <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
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;
}
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,
};
}
}.callback;
}
if (has_one) {
// 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,30 +596,24 @@ 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();
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);
// 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,
};
}
}.callback;
}
if (has_one) {
// 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.
fn zigValueToJs(
@@ -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"));
}

View File

@@ -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 <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
const std = @import("std");
const MemoryPool = std.heap.MemoryPool;

View File

@@ -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 <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
const std = @import("std");
const Allocator = std.mem.Allocator;

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
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" },
}, .{});
}

View File

@@ -1,3 +1,21 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
const std = @import("std");
// TODO: use functions instead of "fake" struct once we handle function API generation

View File

@@ -1,3 +1,21 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("js.zig");
const generate = @import("generate.zig");