Files
bun.sh/src/bun.js/bindings/JSObject.zig
2025-03-02 18:02:20 -08:00

208 lines
8.5 KiB
Zig

const std = @import("std");
const bun = @import("root").bun;
const JSC = bun.JSC;
const Shimmer = JSC.Shimmer;
const JSValue = JSC.JSValue;
const JSGlobalObject = JSC.JSGlobalObject;
const ZigString = JSC.ZigString;
pub const JSObject = extern struct {
pub const shim = Shimmer("JSC", "JSObject", @This());
const cppFn = shim.cppFn;
pub fn toJS(obj: *JSObject) JSValue {
return JSValue.fromCell(obj);
}
/// Non-objects will be runtime-coerced to objects.
///
/// For cells this is `toObjectSlow`, for other types it's `toObjectSlowCase`.
pub fn fromJS(value: JSValue, globalThis: JSValue) *JSObject {
return JSValue.toObject(value, globalThis);
}
/// Returns `null` if the value is not an object.
pub fn tryFromJS(maybe_obj: JSValue, globalThis: *JSC.JSGlobalObject) ?*JSObject {
return JSValue.asObject(maybe_obj, globalThis);
}
/// Marshall a struct instance into a JSObject, copying its properties.
///
/// Each field will be encoded with `JSC.toJS`. Fields whose types have a
/// `toJS` method will have it called to encode.
///
/// This method is equivalent to `Object.create(...)` + setting properties,
/// and is only intended for creating POJOs.
pub fn create(pojo: anytype, global: *JSGlobalObject) *JSObject {
return createFromStructWithPrototype(@TypeOf(pojo), pojo, global, false);
}
/// Marshall a struct into a JSObject, copying its properties. It's
/// `__proto__` will be `null`.
///
/// Each field will be encoded with `JSC.toJS`. Fields whose types have a
/// `toJS` method will have it called to encode.
///
/// This is roughly equivalent to creating an object with
/// `Object.create(null)` and adding properties to it.
pub fn createNullProto(pojo: anytype, global: *JSGlobalObject) *JSObject {
return createFromStructWithPrototype(@TypeOf(pojo), pojo, global, true);
}
/// Marshall a struct instance into a JSObject. `pojo` is borrowed.
///
/// Each field will be encoded with `JSC.toJS`. Fields whose types have a
/// `toJS` method will have it called to encode.
///
/// This method is equivalent to `Object.create(...)` + setting properties,
/// and is only intended for creating POJOs.
///
/// The object's prototype with either be `null` or `ObjectPrototype`
/// depending on whether `null_prototype` is set. Prefer using the object
/// prototype (`null_prototype = false`) unless you have a good reason not
/// to.
fn createFromStructWithPrototype(comptime T: type, pojo: T, global: *JSGlobalObject, comptime null_prototype: bool) *JSObject {
const info: std.builtin.Type.Struct = @typeInfo(T).@"struct";
const obj = obj: {
const val = if (comptime null_prototype)
JSValue.createEmptyObjectWithNullPrototype(global)
else
JSValue.createEmptyObject(global, comptime info.fields.len);
if (bun.Environment.isDebug)
bun.assert(val.isObject());
break :obj val.uncheckedPtrCast(JSObject);
};
const cell = toJS(obj);
inline for (info.fields) |field| {
const property = @field(pojo, field.name);
cell.put(
global,
field.name,
JSC.toJS(global, @TypeOf(property), property, .temporary),
);
}
return obj;
}
pub inline fn put(obj: *JSObject, global: *JSGlobalObject, key: anytype, value: JSValue) !void {
obj.toJS().put(global, key, value);
}
pub inline fn putAllFromStruct(obj: *JSObject, global: *JSGlobalObject, properties: anytype) !void {
inline for (comptime std.meta.fieldNames(@TypeOf(properties))) |field| {
try obj.put(global, field, @field(properties, field));
}
}
/// Equivalent to `target[property]`. Calls userland getters/proxies. Can
/// throw. Null indicates the property does not exist. JavaScript undefined
/// and JavaScript null can exist as a property and is different than zig
/// `null` (property does not exist).
///
/// `property` must be either `[]const u8`. A comptime slice may defer to
/// calling `fastGet`, which use a more optimal code path. This function is
/// marked `inline` to allow Zig to determine if `fastGet` should be used
/// per invocation.
pub inline fn get(target: *JSObject, global: *JSGlobalObject, property: anytype) bun.JSError!?JSValue {
const property_slice: []const u8 = property; // must be a slice!
// This call requires `get` to be `inline`
if (bun.isComptimeKnown(property_slice)) {
if (comptime JSValue.BuiltinName.get(property_slice)) |builtin_name| {
return target.fastGetWithError(global, builtin_name);
}
}
return switch (JSC__JSObject__getIfPropertyExistsImpl(target, global, property_slice.ptr, @intCast(property_slice.len))) {
.zero => error.JSError,
.property_does_not_exist_on_object => null,
// TODO: see bug described in ObjectBindings.cpp
// since there are false positives, the better path is to make them
// negatives, as the number of places that desire throwing on
// existing undefined is extremely small, but non-zero.
.undefined => null,
else => |val| val,
};
}
extern fn JSC__JSObject__getIfPropertyExistsImpl(object: *JSObject, global: *JSGlobalObject, ptr: [*]const u8, len: u32) JSValue;
pub fn fastGetWithError(this: *JSObject, global: *JSGlobalObject, builtin_name: JSValue.BuiltinName) bun.JSError!?JSValue {
return switch (JSC__JSObject__fastGet(this, global, @intFromEnum(builtin_name))) {
.zero => error.JSError,
.undefined => null,
.property_does_not_exist_on_object => null,
else => |val| val,
};
}
extern fn JSC__JSObject__fastGet(object: *JSObject, global: *JSGlobalObject, builtin_name: u32) JSValue;
extern fn JSC__createStructure(*JSC.JSGlobalObject, *JSC.JSCell, u32, names: [*]ExternColumnIdentifier) JSC.JSValue;
pub const ExternColumnIdentifier = extern struct {
tag: u8 = 0,
value: extern union {
index: u32,
name: bun.String,
},
pub fn string(this: *ExternColumnIdentifier) ?*bun.String {
return switch (this.tag) {
2 => &this.value.name,
else => null,
};
}
pub fn deinit(this: *ExternColumnIdentifier) void {
if (this.string()) |str| {
str.deref();
}
}
};
pub fn createStructure(global: *JSGlobalObject, owner: JSC.JSValue, length: u32, names: [*]ExternColumnIdentifier) JSValue {
JSC.markBinding(@src());
return JSC__createStructure(global, owner.asCell(), length, names);
}
const InitializeCallback = *const fn (ctx: *anyopaque, obj: *JSObject, global: *JSGlobalObject) callconv(.C) void;
extern fn JSC__JSObject__create(global_object: *JSGlobalObject, length: usize, ctx: *anyopaque, initializer: InitializeCallback) JSValue;
pub fn Initializer(comptime Ctx: type, comptime func: fn (*Ctx, obj: *JSObject, global: *JSGlobalObject) void) type {
return struct {
pub fn call(this: *anyopaque, obj: *JSObject, global: *JSGlobalObject) callconv(.C) void {
@call(bun.callmod_inline, func, .{ @as(*Ctx, @ptrCast(@alignCast(this))), obj, global });
}
};
}
pub fn createWithInitializer(comptime Ctx: type, creator: *Ctx, global: *JSGlobalObject, length: usize) JSValue {
const Type = Initializer(Ctx, Ctx.create);
return JSC__JSObject__create(global, length, creator, Type.call);
}
pub fn getIndex(this: JSValue, globalThis: *JSGlobalObject, i: u32) JSValue {
return cppFn("getIndex", .{
this,
globalThis,
i,
});
}
pub fn putRecord(this: *JSObject, global: *JSGlobalObject, key: *ZigString, values: []ZigString) void {
return cppFn("putRecord", .{ this, global, key, values.ptr, values.len });
}
extern fn Bun__JSObject__getCodePropertyVMInquiry(*JSGlobalObject, *JSObject) JSValue;
/// This will not call getters or be observable from JavaScript.
pub fn getCodePropertyVMInquiry(obj: *JSObject, global: *JSGlobalObject) ?JSValue {
const v = Bun__JSObject__getCodePropertyVMInquiry(global, obj);
if (v == .zero) return null;
return v;
}
};