Poor support for functions/namespaces.

If you look at the specification for `console` [1], you'll note that it's a
namespace, not an interface (like most things). Furthermore, MDN lists its
methods as "static".

But it's a pretty weird namespace IMO, because some of its "functions", like
`count` can have state associated with them.

This causes some problems with our current implementation. Something like:

```
[1].forEach(console.log)
```

Fails, since `this` isn't our window-attached Console instance.

This commit introducing a new `static_XYZ` naming convention which does not
have the class/Self as a receiver:

```
pub fn static_log(values: []JsObject, page: *Page) !void {
```

This turns Console into a namespace for these specific functions, while still
being used normally for those functions that require state.

We could infer this behavior from the first parameter, but that seems more
error prone. For now, I prefer having the explicit `static_` prefix.

[1] https://console.spec.whatwg.org/#console-namespace
This commit is contained in:
Karl Seguin
2025-06-03 14:40:10 +08:00
parent bde8c54e7e
commit 6451065c77
2 changed files with 49 additions and 9 deletions

View File

@@ -1680,6 +1680,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
}
} else if (comptime std.mem.startsWith(u8, name, "get_")) {
generateProperty(Struct, name[4..], isolate, template_proto);
} else if (comptime std.mem.startsWith(u8, name, "static_")) {
generateFunction(Struct, name[7..], isolate, template_proto);
}
}
@@ -1769,6 +1771,23 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
template_proto.set(js_name, function_template, v8.PropertyAttribute.None);
}
fn generateFunction(comptime Struct: type, comptime name: []const u8, isolate: v8.Isolate, template_proto: v8.ObjectTemplate) void {
const js_name = v8.String.initUtf8(isolate, name).toName();
const function_template = v8.FunctionTemplate.initCallback(isolate, struct {
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
var caller = Caller(Self, State).init(info);
defer caller.deinit();
const named_function = comptime NamedFunction.init(Struct, "static_" ++ name);
caller.function(Struct, named_function, info) catch |err| {
caller.handleError(Struct, named_function, err, info);
};
}
}.callback);
template_proto.set(js_name, function_template, v8.PropertyAttribute.None);
}
fn generateAttribute(comptime Struct: type, comptime name: []const u8, isolate: v8.Isolate, template: v8.FunctionTemplate, template_proto: v8.ObjectTemplate) void {
const zig_value = @field(Struct, name);
const js_value = simpleZigValueToJs(isolate, zig_value, true);
@@ -2154,6 +2173,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return @ptrFromInt(@intFromPtr(toa.ptr) + @as(usize, @intCast(offset)));
}
if (prototype_index == type_index) {
// When a type has itself as the prototype, then we've
// reached the end of the chain.
return error.InvalidArgument;
}
type_index = prototype_index;
@@ -2341,6 +2362,14 @@ fn Caller(comptime E: type, comptime State: type) type {
info.getReturnValue().set(try scope.zigValueToJs(res));
}
fn function(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void {
const scope = self.scope;
const func = @field(Struct, named_function.name);
const args = try self.getArgs(Struct, named_function, 0, info);
const res = @call(.auto, func, args);
info.getReturnValue().set(try scope.zigValueToJs(res));
}
fn getter(self: *Self, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void {
const scope = self.scope;
const func = @field(Struct, named_function.name);