Files
bun.sh/src/ast/Expr.zig
taylor.fish edf13bd91d Refactor BabyList (#22502)
(For internal tracking: fixes STAB-1129, STAB-1145, STAB-1146,
STAB-1150, STAB-1126, STAB-1147, STAB-1148, STAB-1149, STAB-1158)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-09-09 20:41:10 -07:00

3264 lines
102 KiB
Zig

loc: logger.Loc,
data: Data,
pub const empty = Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = logger.Loc.Empty };
pub fn isAnonymousNamed(expr: Expr) bool {
return switch (expr.data) {
.e_arrow => true,
.e_function => |func| func.func.name == null,
.e_class => |class| class.class_name == null,
else => false,
};
}
pub fn clone(this: Expr, allocator: std.mem.Allocator) !Expr {
return .{
.loc = this.loc,
.data = try this.data.clone(allocator),
};
}
pub fn deepClone(this: Expr, allocator: std.mem.Allocator) OOM!Expr {
return .{
.loc = this.loc,
.data = try this.data.deepClone(allocator),
};
}
pub fn wrapInArrow(this: Expr, allocator: std.mem.Allocator) !Expr {
var stmts = try allocator.alloc(Stmt, 1);
stmts[0] = Stmt.alloc(S.Return, S.Return{ .value = this }, this.loc);
return Expr.init(E.Arrow, E.Arrow{
.args = &.{},
.body = .{
.loc = this.loc,
.stmts = stmts,
},
}, this.loc);
}
pub fn canBeInlinedFromPropertyAccess(this: Expr) bool {
return switch (this.data) {
// if the array has a spread we must keep it
// https://github.com/oven-sh/bun/issues/2594
.e_spread => false,
.e_missing => false,
else => true,
};
}
pub fn canBeConstValue(this: Expr) bool {
return this.data.canBeConstValue();
}
pub fn canBeMoved(expr: Expr) bool {
return expr.data.canBeMoved();
}
pub fn unwrapInlined(expr: Expr) Expr {
if (expr.data.as(.e_inlined_enum)) |inlined| return inlined.value;
return expr;
}
pub fn fromBlob(
blob: *const jsc.WebCore.Blob,
allocator: std.mem.Allocator,
mime_type_: ?MimeType,
log: *logger.Log,
loc: logger.Loc,
) !Expr {
const bytes = blob.sharedView();
const mime_type = mime_type_ orelse MimeType.init(blob.content_type, null, null);
if (mime_type.category == .json) {
const source = &logger.Source.initPathString("fetch.json", bytes);
var out_expr = JSONParser.parseForMacro(source, log, allocator) catch {
return error.MacroFailed;
};
out_expr.loc = loc;
switch (out_expr.data) {
.e_object => {
out_expr.data.e_object.was_originally_macro = true;
},
.e_array => {
out_expr.data.e_array.was_originally_macro = true;
},
else => {},
}
return out_expr;
}
if (mime_type.category.isTextLike()) {
var output = MutableString.initEmpty(allocator);
try JSPrinter.quoteForJSON(bytes, &output, true);
var list = output.toOwnedSlice();
// remove the quotes
if (list.len > 0) {
list = list[1 .. list.len - 1];
}
return Expr.init(E.String, E.String.init(list), loc);
}
return Expr.init(
E.String,
E.String{
.data = try jsc.ZigString.init(bytes).toBase64DataURL(allocator),
},
loc,
);
}
pub inline fn initIdentifier(ref: Ref, loc: logger.Loc) Expr {
return Expr{
.loc = loc,
.data = .{
.e_identifier = E.Identifier.init(ref),
},
};
}
pub fn toEmpty(expr: Expr) Expr {
return Expr{ .data = .{ .e_missing = E.Missing{} }, .loc = expr.loc };
}
pub fn isEmpty(expr: Expr) bool {
return expr.data == .e_missing;
}
pub const Query = struct { expr: Expr, loc: logger.Loc, i: u32 = 0 };
pub fn hasAnyPropertyNamed(expr: *const Expr, comptime names: []const string) bool {
if (std.meta.activeTag(expr.data) != .e_object) return false;
const obj = expr.data.e_object;
if (obj.properties.len == 0) return false;
for (obj.properties.slice()) |prop| {
if (prop.value == null) continue;
const key = prop.key orelse continue;
if (std.meta.activeTag(key.data) != .e_string) continue;
const key_str = key.data.e_string;
if (strings.eqlAnyComptime(key_str.data, names)) return true;
}
return false;
}
pub fn toJS(this: Expr, allocator: std.mem.Allocator, globalObject: *jsc.JSGlobalObject) ToJSError!jsc.JSValue {
return this.data.toJS(allocator, globalObject);
}
pub inline fn isArray(this: *const Expr) bool {
return this.data == .e_array;
}
pub inline fn isObject(this: *const Expr) bool {
return this.data == .e_object;
}
pub fn get(expr: *const Expr, name: string) ?Expr {
return if (asProperty(expr, name)) |query| query.expr else null;
}
/// Only use this for pretty-printing JSON. Do not use in transpiler.
///
/// This does not handle edgecases like `-1` or stringifying arbitrary property lookups.
pub fn getByIndex(expr: *const Expr, index: u32, index_str: string, allocator: std.mem.Allocator) ?Expr {
switch (expr.data) {
.e_array => |array| {
if (index >= array.items.len) return null;
return array.items.slice()[index];
},
.e_object => |object| {
for (object.properties.sliceConst()) |*prop| {
const key = &(prop.key orelse continue);
switch (key.data) {
.e_string => |str| {
if (str.eql(string, index_str)) {
return prop.value;
}
},
.e_number => |num| {
if (num.toU32() == index) {
return prop.value;
}
},
else => {},
}
}
return null;
},
.e_string => |str| {
if (str.len() > index) {
var slice = str.slice(allocator);
// TODO: this is not correct since .length refers to UTF-16 code units and not UTF-8 bytes
// However, since this is only used in the JSON prettifier for `bun pm view`, it's not a blocker for shipping.
if (slice.len > index) {
return Expr.init(E.String, .{ .data = slice[index..][0..1] }, expr.loc);
}
}
},
else => {},
}
return null;
}
/// This supports lookups like:
/// - `foo`
/// - `foo.bar`
/// - `foo[123]`
/// - `foo[123].bar`
/// - `foo[123].bar[456]`
/// - `foo[123].bar[456].baz`
/// - `foo[123].bar[456].baz.qux` // etc.
///
/// This is not intended for use by the transpiler, instead by pretty printing JSON.
pub fn getPathMayBeIndex(expr: *const Expr, name: string) ?Expr {
if (name.len == 0) {
return null;
}
if (strings.indexOfAny(name, "[.")) |idx| {
switch (name[idx]) {
'[' => {
const end_idx = strings.indexOfChar(name, ']') orelse return null;
var base_expr = expr;
if (idx > 0) {
const key = name[0..idx];
base_expr = &(base_expr.get(key) orelse return null);
}
const index_str = name[idx + 1 .. end_idx];
const index = std.fmt.parseInt(u32, index_str, 10) catch return null;
const rest = if (name.len > end_idx) name[end_idx + 1 ..] else "";
const result = &(base_expr.getByIndex(index, index_str, bun.default_allocator) orelse return null);
if (rest.len > 0) return result.getPathMayBeIndex(rest);
return result.*;
},
'.' => {
const key = name[0..idx];
const sub_expr = &(expr.get(key) orelse return null);
const subpath = if (name.len > idx) name[idx + 1 ..] else "";
if (subpath.len > 0) {
return sub_expr.getPathMayBeIndex(subpath);
}
return sub_expr.*;
},
else => unreachable,
}
}
return expr.get(name);
}
/// Don't use this if you care about performance.
///
/// Sets the value of a property, creating it if it doesn't exist.
/// `expr` must be an object.
pub fn set(expr: *Expr, allocator: std.mem.Allocator, name: string, value: Expr) OOM!void {
bun.assertWithLocation(expr.isObject(), @src());
for (0..expr.data.e_object.properties.len) |i| {
const prop = &expr.data.e_object.properties.ptr[i];
const key = prop.key orelse continue;
if (std.meta.activeTag(key.data) != .e_string) continue;
if (key.data.e_string.eql(string, name)) {
prop.value = value;
return;
}
}
try expr.data.e_object.properties.append(allocator, .{
.key = Expr.init(E.String, .{ .data = name }, logger.Loc.Empty),
.value = value,
});
}
/// Don't use this if you care about performance.
///
/// Sets the value of a property to a string, creating it if it doesn't exist.
/// `expr` must be an object.
pub fn setString(expr: *Expr, allocator: std.mem.Allocator, name: string, value: string) OOM!void {
bun.assertWithLocation(expr.isObject(), @src());
for (0..expr.data.e_object.properties.len) |i| {
const prop = &expr.data.e_object.properties.ptr[i];
const key = prop.key orelse continue;
if (std.meta.activeTag(key.data) != .e_string) continue;
if (key.data.e_string.eql(string, name)) {
prop.value = Expr.init(E.String, .{ .data = value }, logger.Loc.Empty);
return;
}
}
try expr.data.e_object.properties.append(allocator, .{
.key = Expr.init(E.String, .{ .data = name }, logger.Loc.Empty),
.value = Expr.init(E.String, .{ .data = value }, logger.Loc.Empty),
});
}
pub fn getObject(expr: *const Expr, name: string) ?Expr {
if (expr.asProperty(name)) |query| {
if (query.expr.isObject()) {
return query.expr;
}
}
return null;
}
pub fn getString(expr: *const Expr, allocator: std.mem.Allocator, name: string) OOM!?struct { string, logger.Loc } {
if (asProperty(expr, name)) |q| {
if (q.expr.asString(allocator)) |str| {
return .{
str,
q.expr.loc,
};
}
}
return null;
}
pub fn getNumber(expr: *const Expr, name: string) ?struct { f64, logger.Loc } {
if (asProperty(expr, name)) |q| {
if (q.expr.asNumber()) |num| {
return .{
num,
q.expr.loc,
};
}
}
return null;
}
pub fn getStringCloned(expr: *const Expr, allocator: std.mem.Allocator, name: string) OOM!?string {
return if (asProperty(expr, name)) |q| q.expr.asStringCloned(allocator) else null;
}
pub fn getStringClonedZ(expr: *const Expr, allocator: std.mem.Allocator, name: string) OOM!?stringZ {
return if (asProperty(expr, name)) |q| q.expr.asStringZ(allocator) else null;
}
pub fn getArray(expr: *const Expr, name: string) ?ArrayIterator {
return if (asProperty(expr, name)) |q| q.expr.asArray() else null;
}
pub fn getRope(self: *const Expr, rope: *const E.Object.Rope) ?E.Object.RopeQuery {
if (self.get(rope.head.data.e_string.data)) |existing| {
switch (existing.data) {
.e_array => |array| {
if (rope.next) |next| {
if (array.items.last()) |end| {
return end.getRope(next);
}
}
return E.Object.RopeQuery{
.expr = existing,
.rope = rope,
};
},
.e_object => {
if (rope.next) |next| {
if (existing.getRope(next)) |end| {
return end;
}
}
return E.Object.RopeQuery{
.expr = existing,
.rope = rope,
};
},
else => return E.Object.RopeQuery{
.expr = existing,
.rope = rope,
},
}
}
return null;
}
// Making this comptime bloats the binary and doesn't seem to impact runtime performance.
pub fn asProperty(expr: *const Expr, name: string) ?Query {
if (std.meta.activeTag(expr.data) != .e_object) return null;
const obj = expr.data.e_object;
if (obj.properties.len == 0) return null;
return obj.asProperty(name);
}
pub fn asPropertyStringMap(expr: *const Expr, name: string, allocator: std.mem.Allocator) ?*bun.StringArrayHashMap(string) {
if (std.meta.activeTag(expr.data) != .e_object) return null;
const obj_ = expr.data.e_object;
if (obj_.properties.len == 0) return null;
const query = obj_.asProperty(name) orelse return null;
if (query.expr.data != .e_object) return null;
const obj = query.expr.data.e_object;
var count: usize = 0;
for (obj.properties.slice()) |prop| {
const key = prop.key.?.asString(allocator) orelse continue;
const value = prop.value.?.asString(allocator) orelse continue;
count += @as(usize, @intFromBool(key.len > 0 and value.len > 0));
}
if (count == 0) return null;
var map = bun.StringArrayHashMap(string).init(allocator);
map.ensureUnusedCapacity(count) catch return null;
for (obj.properties.slice()) |prop| {
const key = prop.key.?.asString(allocator) orelse continue;
const value = prop.value.?.asString(allocator) orelse continue;
if (!(key.len > 0 and value.len > 0)) continue;
map.putAssumeCapacity(key, value);
}
const ptr = allocator.create(bun.StringArrayHashMap(string)) catch unreachable;
ptr.* = map;
return ptr;
}
pub const ArrayIterator = struct {
array: *const E.Array,
index: u32,
pub fn next(this: *ArrayIterator) ?Expr {
if (this.index >= this.array.items.len) {
return null;
}
defer this.index += 1;
return this.array.items.ptr[this.index];
}
};
pub fn asArray(expr: *const Expr) ?ArrayIterator {
if (std.meta.activeTag(expr.data) != .e_array) return null;
const array = expr.data.e_array;
if (array.items.len == 0) return null;
return ArrayIterator{ .array = array, .index = 0 };
}
pub inline fn asUtf8StringLiteral(expr: *const Expr) ?string {
if (expr.data == .e_string) {
bun.debugAssert(expr.data.e_string.next == null);
return expr.data.e_string.data;
}
return null;
}
pub inline fn asStringLiteral(expr: *const Expr, allocator: std.mem.Allocator) ?string {
if (std.meta.activeTag(expr.data) != .e_string) return null;
return expr.data.e_string.string(allocator) catch null;
}
pub inline fn isString(expr: *const Expr) bool {
return switch (expr.data) {
.e_string => true,
else => false,
};
}
pub inline fn asString(expr: *const Expr, allocator: std.mem.Allocator) ?string {
switch (expr.data) {
.e_string => |str| return bun.handleOom(str.string(allocator)),
else => return null,
}
}
pub inline fn asStringHash(expr: *const Expr, allocator: std.mem.Allocator, comptime hash_fn: *const fn (buf: []const u8) callconv(.Inline) u64) OOM!?u64 {
switch (expr.data) {
.e_string => |str| {
if (str.isUTF8()) return hash_fn(str.data);
const utf8_str = try str.string(allocator);
defer allocator.free(utf8_str);
return hash_fn(utf8_str);
},
else => return null,
}
}
pub inline fn asStringCloned(expr: *const Expr, allocator: std.mem.Allocator) OOM!?string {
switch (expr.data) {
.e_string => |str| return try str.stringCloned(allocator),
else => return null,
}
}
pub inline fn asStringZ(expr: *const Expr, allocator: std.mem.Allocator) OOM!?stringZ {
switch (expr.data) {
.e_string => |str| return try str.stringZ(allocator),
else => return null,
}
}
pub fn asBool(
expr: *const Expr,
) ?bool {
if (std.meta.activeTag(expr.data) != .e_boolean) return null;
return expr.data.e_boolean.value;
}
pub fn asNumber(expr: *const Expr) ?f64 {
if (expr.data != .e_number) return null;
return expr.data.e_number.value;
}
pub const EFlags = enum { none, ts_decorator };
const Serializable = struct {
type: Tag,
object: string,
value: Data,
loc: logger.Loc,
};
pub fn isMissing(a: *const Expr) bool {
return std.meta.activeTag(a.data) == Expr.Tag.e_missing;
}
// The goal of this function is to "rotate" the AST if it's possible to use the
// left-associative property of the operator to avoid unnecessary parentheses.
//
// When using this, make absolutely sure that the operator is actually
// associative. For example, the "-" operator is not associative for
// floating-point numbers.
pub fn joinWithLeftAssociativeOp(
comptime op: Op.Code,
a: Expr,
b: Expr,
allocator: std.mem.Allocator,
) Expr {
// "(a, b) op c" => "a, b op c"
switch (a.data) {
.e_binary => |comma| {
if (comma.op == .bin_comma) {
comma.right = joinWithLeftAssociativeOp(op, comma.right, b, allocator);
}
},
else => {},
}
// "a op (b op c)" => "(a op b) op c"
// "a op (b op (c op d))" => "((a op b) op c) op d"
switch (b.data) {
.e_binary => |binary| {
if (binary.op == op) {
return joinWithLeftAssociativeOp(
op,
joinWithLeftAssociativeOp(op, a, binary.left, allocator),
binary.right,
allocator,
);
}
},
else => {},
}
// "a op b" => "a op b"
// "(a op b) op c" => "(a op b) op c"
return Expr.init(E.Binary, E.Binary{ .op = op, .left = a, .right = b }, a.loc);
}
pub fn joinWithComma(a: Expr, b: Expr, _: std.mem.Allocator) Expr {
if (a.isMissing()) {
return b;
}
if (b.isMissing()) {
return a;
}
return Expr.init(E.Binary, E.Binary{ .op = .bin_comma, .left = a, .right = b }, a.loc);
}
pub fn joinAllWithComma(all: []Expr, allocator: std.mem.Allocator) Expr {
bun.assert(all.len > 0);
switch (all.len) {
1 => {
return all[0];
},
2 => {
return Expr.joinWithComma(all[0], all[1], allocator);
},
else => {
var expr = all[0];
for (1..all.len) |i| {
expr = Expr.joinWithComma(expr, all[i], allocator);
}
return expr;
},
}
}
pub fn joinAllWithCommaCallback(all: []Expr, comptime Context: type, ctx: Context, comptime callback: (fn (ctx: anytype, expr: Expr) ?Expr), allocator: std.mem.Allocator) ?Expr {
switch (all.len) {
0 => return null,
1 => {
return callback(ctx, all[0]);
},
2 => {
return Expr.joinWithComma(
callback(ctx, all[0]) orelse Expr{
.data = .{ .e_missing = .{} },
.loc = all[0].loc,
},
callback(ctx, all[1]) orelse Expr{
.data = .{ .e_missing = .{} },
.loc = all[1].loc,
},
allocator,
);
},
else => {
var i: usize = 1;
var expr = callback(ctx, all[0]) orelse Expr{
.data = .{ .e_missing = .{} },
.loc = all[0].loc,
};
while (i < all.len) : (i += 1) {
expr = Expr.joinWithComma(expr, callback(ctx, all[i]) orelse Expr{
.data = .{ .e_missing = .{} },
.loc = all[i].loc,
}, allocator);
}
return expr;
},
}
}
pub fn jsonStringify(self: *const @This(), writer: anytype) !void {
return try writer.write(Serializable{ .type = std.meta.activeTag(self.data), .object = "expr", .value = self.data, .loc = self.loc });
}
pub fn extractNumericValuesInSafeRange(left: Expr.Data, right: Expr.Data) ?[2]f64 {
const l_value = left.extractNumericValue() orelse return null;
const r_value = right.extractNumericValue() orelse return null;
// Check for NaN and return null if either value is NaN
if (std.math.isNan(l_value) or std.math.isNan(r_value)) {
return null;
}
if (std.math.isInf(l_value) or std.math.isInf(r_value)) {
return .{ l_value, r_value };
}
if (l_value > bun.jsc.MAX_SAFE_INTEGER or r_value > bun.jsc.MAX_SAFE_INTEGER) {
return null;
}
if (l_value < bun.jsc.MIN_SAFE_INTEGER or r_value < bun.jsc.MIN_SAFE_INTEGER) {
return null;
}
return .{ l_value, r_value };
}
pub fn extractNumericValues(left: Expr.Data, right: Expr.Data) ?[2]f64 {
return .{
left.extractNumericValue() orelse return null,
right.extractNumericValue() orelse return null,
};
}
pub fn extractStringValues(left: Expr.Data, right: Expr.Data, allocator: std.mem.Allocator) ?[2]*E.String {
const l_string = left.extractStringValue() orelse return null;
const r_string = right.extractStringValue() orelse return null;
l_string.resolveRopeIfNeeded(allocator);
r_string.resolveRopeIfNeeded(allocator);
if (l_string.isUTF8() != r_string.isUTF8()) return null;
return .{
l_string,
r_string,
};
}
pub var icount: usize = 0;
// We don't need to dynamically allocate booleans
var true_bool = E.Boolean{ .value = true };
var false_bool = E.Boolean{ .value = false };
var bool_values = [_]*E.Boolean{ &false_bool, &true_bool };
/// When the lifetime of an Expr.Data's pointer must exist longer than reset() is called, use this function.
/// Be careful to free the memory (or use an allocator that does it for you)
/// Also, prefer Expr.init or Expr.alloc when possible. This will be slower.
pub fn allocate(allocator: std.mem.Allocator, comptime Type: type, st: Type, loc: logger.Loc) Expr {
icount += 1;
Data.Store.assert();
switch (Type) {
E.Array => {
return Expr{
.loc = loc,
.data = Data{
.e_array = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Class => {
return Expr{
.loc = loc,
.data = Data{
.e_class = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Unary => {
return Expr{
.loc = loc,
.data = Data{
.e_unary = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Binary => {
return Expr{
.loc = loc,
.data = Data{
.e_binary = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.This => {
return Expr{
.loc = loc,
.data = Data{
.e_this = st,
},
};
},
E.Boolean => {
return Expr{
.loc = loc,
.data = Data{
.e_boolean = st,
},
};
},
E.Super => {
return Expr{
.loc = loc,
.data = Data{
.e_super = st,
},
};
},
E.Null => {
return Expr{
.loc = loc,
.data = Data{
.e_null = st,
},
};
},
E.Undefined => {
return Expr{
.loc = loc,
.data = Data{
.e_undefined = st,
},
};
},
E.New => {
return Expr{
.loc = loc,
.data = Data{
.e_new = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.NewTarget => {
return Expr{
.loc = loc,
.data = Data{
.e_new_target = st,
},
};
},
E.Function => {
return Expr{
.loc = loc,
.data = Data{
.e_function = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.ImportMeta => {
return Expr{
.loc = loc,
.data = Data{
.e_import_meta = st,
},
};
},
E.Call => {
return Expr{
.loc = loc,
.data = Data{
.e_call = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Dot => {
return Expr{
.loc = loc,
.data = Data{
.e_dot = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Index => {
return Expr{
.loc = loc,
.data = Data{
.e_index = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Arrow => {
return Expr{
.loc = loc,
.data = Data{
.e_arrow = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Identifier => {
return Expr{
.loc = loc,
.data = Data{
.e_identifier = E.Identifier{
.ref = st.ref,
.must_keep_due_to_with_stmt = st.must_keep_due_to_with_stmt,
.can_be_removed_if_unused = st.can_be_removed_if_unused,
.call_can_be_unwrapped_if_unused = st.call_can_be_unwrapped_if_unused,
},
},
};
},
E.ImportIdentifier => {
return Expr{
.loc = loc,
.data = Data{
.e_import_identifier = .{
.ref = st.ref,
.was_originally_identifier = st.was_originally_identifier,
},
},
};
},
E.CommonJSExportIdentifier => {
return Expr{
.loc = loc,
.data = Data{
.e_commonjs_export_identifier = .{
.ref = st.ref,
},
},
};
},
E.PrivateIdentifier => {
return Expr{
.loc = loc,
.data = Data{
.e_private_identifier = st,
},
};
},
E.JSXElement => {
return Expr{
.loc = loc,
.data = Data{
.e_jsx_element = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Missing => {
return Expr{ .loc = loc, .data = Data{ .e_missing = E.Missing{} } };
},
E.Number => {
return Expr{
.loc = loc,
.data = Data{
.e_number = st,
},
};
},
E.BigInt => {
return Expr{
.loc = loc,
.data = Data{
.e_big_int = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Object => {
return Expr{
.loc = loc,
.data = Data{
.e_object = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Spread => {
return Expr{
.loc = loc,
.data = Data{
.e_spread = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.String => {
if (comptime Environment.isDebug) {
// Sanity check: assert string is not a null ptr
if (st.data.len > 0 and st.isUTF8()) {
bun.assert(@intFromPtr(st.data.ptr) > 0);
}
}
return Expr{
.loc = loc,
.data = Data{
.e_string = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Template => {
return Expr{
.loc = loc,
.data = Data{
.e_template = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.RegExp => {
return Expr{
.loc = loc,
.data = Data{
.e_reg_exp = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Await => {
return Expr{
.loc = loc,
.data = Data{
.e_await = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.Yield => {
return Expr{
.loc = loc,
.data = Data{
.e_yield = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.If => {
return Expr{
.loc = loc,
.data = Data{
.e_if = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.RequireResolveString => {
return Expr{
.loc = loc,
.data = Data{
.e_require_resolve_string = st,
},
};
},
E.Import => {
return Expr{
.loc = loc,
.data = Data{
.e_import = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st;
break :brk item;
},
},
};
},
E.RequireString => {
return Expr{
.loc = loc,
.data = Data{
.e_require_string = st,
},
};
},
*E.String => {
return Expr{
.loc = loc,
.data = Data{
.e_string = brk: {
const item = allocator.create(Type) catch unreachable;
item.* = st.*;
break :brk item;
},
},
};
},
else => {
@compileError("Invalid type passed to Expr.init: " ++ @typeName(Type));
},
}
}
pub const Disabler = bun.DebugOnlyDisabler(@This());
pub fn init(comptime Type: type, st: Type, loc: logger.Loc) Expr {
icount += 1;
Data.Store.assert();
switch (Type) {
E.NameOfSymbol => {
return Expr{
.loc = loc,
.data = Data{
.e_name_of_symbol = Data.Store.append(E.NameOfSymbol, st),
},
};
},
E.Array => {
return Expr{
.loc = loc,
.data = Data{
.e_array = Data.Store.append(Type, st),
},
};
},
E.Class => {
return Expr{
.loc = loc,
.data = Data{
.e_class = Data.Store.append(Type, st),
},
};
},
E.Unary => {
return Expr{
.loc = loc,
.data = Data{
.e_unary = Data.Store.append(Type, st),
},
};
},
E.Binary => {
return Expr{
.loc = loc,
.data = Data{
.e_binary = Data.Store.append(Type, st),
},
};
},
E.This => {
return Expr{
.loc = loc,
.data = Data{
.e_this = st,
},
};
},
E.Boolean => {
return Expr{
.loc = loc,
.data = Data{
.e_boolean = st,
},
};
},
E.Super => {
return Expr{
.loc = loc,
.data = Data{
.e_super = st,
},
};
},
E.Null => {
return Expr{
.loc = loc,
.data = Data{
.e_null = st,
},
};
},
E.Undefined => {
return Expr{
.loc = loc,
.data = Data{
.e_undefined = st,
},
};
},
E.New => {
return Expr{
.loc = loc,
.data = Data{
.e_new = Data.Store.append(Type, st),
},
};
},
E.NewTarget => {
return Expr{
.loc = loc,
.data = Data{
.e_new_target = st,
},
};
},
E.Function => {
return Expr{
.loc = loc,
.data = Data{
.e_function = Data.Store.append(Type, st),
},
};
},
E.ImportMeta => {
return Expr{
.loc = loc,
.data = Data{
.e_import_meta = st,
},
};
},
E.Call => {
return Expr{
.loc = loc,
.data = Data{
.e_call = Data.Store.append(Type, st),
},
};
},
E.Dot => {
return Expr{
.loc = loc,
.data = Data{
.e_dot = Data.Store.append(Type, st),
},
};
},
E.Index => {
return Expr{
.loc = loc,
.data = Data{
.e_index = Data.Store.append(Type, st),
},
};
},
E.Arrow => {
return Expr{
.loc = loc,
.data = Data{
.e_arrow = Data.Store.append(Type, st),
},
};
},
E.Identifier => {
return Expr{
.loc = loc,
.data = Data{
.e_identifier = E.Identifier{
.ref = st.ref,
.must_keep_due_to_with_stmt = st.must_keep_due_to_with_stmt,
.can_be_removed_if_unused = st.can_be_removed_if_unused,
.call_can_be_unwrapped_if_unused = st.call_can_be_unwrapped_if_unused,
},
},
};
},
E.ImportIdentifier => {
return Expr{
.loc = loc,
.data = Data{
.e_import_identifier = .{
.ref = st.ref,
.was_originally_identifier = st.was_originally_identifier,
},
},
};
},
E.CommonJSExportIdentifier => {
return Expr{
.loc = loc,
.data = Data{
.e_commonjs_export_identifier = .{
.ref = st.ref,
.base = st.base,
},
},
};
},
E.PrivateIdentifier => {
return Expr{
.loc = loc,
.data = Data{
.e_private_identifier = st,
},
};
},
E.JSXElement => {
return Expr{
.loc = loc,
.data = Data{
.e_jsx_element = Data.Store.append(Type, st),
},
};
},
E.Missing => {
return Expr{ .loc = loc, .data = Data{ .e_missing = E.Missing{} } };
},
E.Number => {
return Expr{
.loc = loc,
.data = Data{
.e_number = st,
},
};
},
E.BigInt => {
return Expr{
.loc = loc,
.data = Data{
.e_big_int = Data.Store.append(Type, st),
},
};
},
E.Object => {
return Expr{
.loc = loc,
.data = Data{
.e_object = Data.Store.append(Type, st),
},
};
},
E.Spread => {
return Expr{
.loc = loc,
.data = Data{
.e_spread = Data.Store.append(Type, st),
},
};
},
E.String => {
if (comptime Environment.isDebug) {
// Sanity check: assert string is not a null ptr
if (st.data.len > 0 and st.isUTF8()) {
bun.assert(@intFromPtr(st.data.ptr) > 0);
}
}
return Expr{
.loc = loc,
.data = Data{
.e_string = Data.Store.append(Type, st),
},
};
},
E.Template => {
return Expr{
.loc = loc,
.data = Data{
.e_template = Data.Store.append(Type, st),
},
};
},
E.RegExp => {
return Expr{
.loc = loc,
.data = Data{
.e_reg_exp = Data.Store.append(Type, st),
},
};
},
E.Await => {
return Expr{
.loc = loc,
.data = Data{
.e_await = Data.Store.append(Type, st),
},
};
},
E.Yield => {
return Expr{
.loc = loc,
.data = Data{
.e_yield = Data.Store.append(Type, st),
},
};
},
E.If => {
return Expr{
.loc = loc,
.data = Data{
.e_if = Data.Store.append(Type, st),
},
};
},
E.RequireResolveString => {
return Expr{
.loc = loc,
.data = Data{
.e_require_resolve_string = st,
},
};
},
E.Import => {
return Expr{
.loc = loc,
.data = Data{
.e_import = Data.Store.append(Type, st),
},
};
},
E.RequireString => {
return Expr{
.loc = loc,
.data = Data{
.e_require_string = st,
},
};
},
*E.String => {
return Expr{
.loc = loc,
.data = Data{
.e_string = Data.Store.append(@TypeOf(st.*), st.*),
},
};
},
E.InlinedEnum => return .{ .loc = loc, .data = .{
.e_inlined_enum = Data.Store.append(@TypeOf(st), st),
} },
else => {
@compileError("Invalid type passed to Expr.init: " ++ @typeName(Type));
},
}
}
/// If this returns true, then calling this expression captures the target of
/// the property access as "this" when calling the function in the property.
pub inline fn isPropertyAccess(this: *const Expr) bool {
return this.hasValueForThisInCall();
}
pub inline fn isPrimitiveLiteral(this: *const Expr) bool {
return @as(Tag, this.data).isPrimitiveLiteral();
}
pub inline fn isRef(this: *const Expr, ref: Ref) bool {
return switch (this.data) {
.e_import_identifier => |import_identifier| import_identifier.ref.eql(ref),
.e_identifier => |ident| ident.ref.eql(ref),
else => false,
};
}
pub const Tag = enum {
e_array,
e_unary,
e_binary,
e_class,
e_new,
e_function,
e_call,
e_dot,
e_index,
e_arrow,
e_jsx_element,
e_object,
e_spread,
e_template,
e_reg_exp,
e_await,
e_yield,
e_if,
e_import,
e_identifier,
e_import_identifier,
e_private_identifier,
e_commonjs_export_identifier,
e_boolean,
e_number,
e_big_int,
e_string,
e_require_string,
e_require_resolve_string,
e_require_call_target,
e_require_resolve_call_target,
e_missing,
e_this,
e_super,
e_null,
e_undefined,
e_new_target,
e_import_meta,
e_import_meta_main,
e_require_main,
e_special,
e_inlined_enum,
e_name_of_symbol,
// object, regex and array may have had side effects
pub fn isPrimitiveLiteral(tag: Tag) bool {
return switch (tag) {
.e_null, .e_undefined, .e_string, .e_boolean, .e_number, .e_big_int => true,
else => false,
};
}
pub fn typeof(tag: Tag) ?string {
return switch (tag) {
.e_array, .e_object, .e_null, .e_reg_exp => "object",
.e_undefined => "undefined",
.e_boolean => "boolean",
.e_number => "number",
.e_big_int => "bigint",
.e_string => "string",
.e_class, .e_function, .e_arrow => "function",
else => null,
};
}
pub fn format(tag: Tag, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try switch (tag) {
.e_string => writer.writeAll("string"),
.e_array => writer.writeAll("array"),
.e_unary => writer.writeAll("unary"),
.e_binary => writer.writeAll("binary"),
.e_boolean => writer.writeAll("boolean"),
.e_super => writer.writeAll("super"),
.e_null => writer.writeAll("null"),
.e_undefined => writer.writeAll("undefined"),
.e_new => writer.writeAll("new"),
.e_function => writer.writeAll("function"),
.e_new_target => writer.writeAll("new target"),
.e_import_meta => writer.writeAll("import.meta"),
.e_call => writer.writeAll("call"),
.e_dot => writer.writeAll("dot"),
.e_index => writer.writeAll("index"),
.e_arrow => writer.writeAll("arrow"),
.e_identifier => writer.writeAll("identifier"),
.e_import_identifier => writer.writeAll("import identifier"),
.e_private_identifier => writer.writeAll("#privateIdentifier"),
.e_jsx_element => writer.writeAll("<jsx>"),
.e_missing => writer.writeAll("<missing>"),
.e_number => writer.writeAll("number"),
.e_big_int => writer.writeAll("BigInt"),
.e_object => writer.writeAll("object"),
.e_spread => writer.writeAll("..."),
.e_template => writer.writeAll("template"),
.e_reg_exp => writer.writeAll("regexp"),
.e_await => writer.writeAll("await"),
.e_yield => writer.writeAll("yield"),
.e_if => writer.writeAll("if"),
.e_require_resolve_string => writer.writeAll("require_or_require_resolve"),
.e_import => writer.writeAll("import"),
.e_this => writer.writeAll("this"),
.e_class => writer.writeAll("class"),
.e_require_string => writer.writeAll("require"),
else => writer.writeAll(@tagName(tag)),
};
}
pub fn jsonStringify(self: @This(), writer: anytype) !void {
return try writer.write(@tagName(self));
}
pub fn isArray(self: Tag) bool {
switch (self) {
.e_array => {
return true;
},
else => {
return false;
},
}
}
pub fn isUnary(self: Tag) bool {
switch (self) {
.e_unary => {
return true;
},
else => {
return false;
},
}
}
pub fn isBinary(self: Tag) bool {
switch (self) {
.e_binary => {
return true;
},
else => {
return false;
},
}
}
pub fn isThis(self: Tag) bool {
switch (self) {
.e_this => {
return true;
},
else => {
return false;
},
}
}
pub fn isClass(self: Tag) bool {
switch (self) {
.e_class => {
return true;
},
else => {
return false;
},
}
}
pub fn isBoolean(self: Tag) bool {
switch (self) {
.e_boolean => {
return true;
},
else => {
return false;
},
}
}
pub fn isSuper(self: Tag) bool {
switch (self) {
.e_super => {
return true;
},
else => {
return false;
},
}
}
pub fn isNull(self: Tag) bool {
switch (self) {
.e_null => {
return true;
},
else => {
return false;
},
}
}
pub fn isUndefined(self: Tag) bool {
switch (self) {
.e_undefined => {
return true;
},
else => {
return false;
},
}
}
pub fn isNew(self: Tag) bool {
switch (self) {
.e_new => {
return true;
},
else => {
return false;
},
}
}
pub fn isNewTarget(self: Tag) bool {
switch (self) {
.e_new_target => {
return true;
},
else => {
return false;
},
}
}
pub fn isFunction(self: Tag) bool {
switch (self) {
.e_function => {
return true;
},
else => {
return false;
},
}
}
pub fn isImportMeta(self: Tag) bool {
switch (self) {
.e_import_meta => {
return true;
},
else => {
return false;
},
}
}
pub fn isCall(self: Tag) bool {
switch (self) {
.e_call => {
return true;
},
else => {
return false;
},
}
}
pub fn isDot(self: Tag) bool {
switch (self) {
.e_dot => {
return true;
},
else => {
return false;
},
}
}
pub fn isIndex(self: Tag) bool {
switch (self) {
.e_index => {
return true;
},
else => {
return false;
},
}
}
pub fn isArrow(self: Tag) bool {
switch (self) {
.e_arrow => {
return true;
},
else => {
return false;
},
}
}
pub fn isIdentifier(self: Tag) bool {
switch (self) {
.e_identifier => {
return true;
},
else => {
return false;
},
}
}
pub fn isImportIdentifier(self: Tag) bool {
switch (self) {
.e_import_identifier => {
return true;
},
else => {
return false;
},
}
}
pub fn isPrivateIdentifier(self: Tag) bool {
switch (self) {
.e_private_identifier => {
return true;
},
else => {
return false;
},
}
}
pub fn isJsxElement(self: Tag) bool {
switch (self) {
.e_jsx_element => {
return true;
},
else => {
return false;
},
}
}
pub fn isMissing(self: Tag) bool {
switch (self) {
.e_missing => {
return true;
},
else => {
return false;
},
}
}
pub fn isNumber(self: Tag) bool {
switch (self) {
.e_number => {
return true;
},
else => {
return false;
},
}
}
pub fn isBigInt(self: Tag) bool {
switch (self) {
.e_big_int => {
return true;
},
else => {
return false;
},
}
}
pub fn isObject(self: Tag) bool {
switch (self) {
.e_object => {
return true;
},
else => {
return false;
},
}
}
pub fn isSpread(self: Tag) bool {
switch (self) {
.e_spread => {
return true;
},
else => {
return false;
},
}
}
pub fn isString(self: Tag) bool {
switch (self) {
.e_string => {
return true;
},
else => {
return false;
},
}
}
pub fn isTemplate(self: Tag) bool {
switch (self) {
.e_template => {
return true;
},
else => {
return false;
},
}
}
pub fn isRegExp(self: Tag) bool {
switch (self) {
.e_reg_exp => {
return true;
},
else => {
return false;
},
}
}
pub fn isAwait(self: Tag) bool {
switch (self) {
.e_await => {
return true;
},
else => {
return false;
},
}
}
pub fn isYield(self: Tag) bool {
switch (self) {
.e_yield => {
return true;
},
else => {
return false;
},
}
}
pub fn isIf(self: Tag) bool {
switch (self) {
.e_if => {
return true;
},
else => {
return false;
},
}
}
pub fn isRequireResolveString(self: Tag) bool {
switch (self) {
.e_require_resolve_string => {
return true;
},
else => {
return false;
},
}
}
pub fn isImport(self: Tag) bool {
switch (self) {
.e_import => {
return true;
},
else => {
return false;
},
}
}
};
pub fn isBoolean(a: *const Expr) bool {
return switch (a.data) {
.e_boolean => true,
.e_if => |ex| ex.yes.isBoolean() and ex.no.isBoolean(),
.e_unary => |ex| ex.op == .un_not or ex.op == .un_delete,
.e_binary => |ex| switch (ex.op) {
.bin_strict_eq, .bin_strict_ne, .bin_loose_eq, .bin_loose_ne, .bin_lt, .bin_gt, .bin_le, .bin_ge, .bin_instanceof, .bin_in => true,
.bin_logical_or => ex.left.isBoolean() and ex.right.isBoolean(),
.bin_logical_and => ex.left.isBoolean() and ex.right.isBoolean(),
else => false,
},
else => false,
};
}
pub fn assign(a: Expr, b: Expr) Expr {
return init(E.Binary, E.Binary{
.op = .bin_assign,
.left = a,
.right = b,
}, a.loc);
}
pub inline fn at(expr: *const Expr, comptime Type: type, t: Type, _: std.mem.Allocator) Expr {
return init(Type, t, expr.loc);
}
// Wraps the provided expression in the "!" prefix operator. The expression
// will potentially be simplified to avoid generating unnecessary extra "!"
// operators. For example, calling this with "!!x" will return "!x" instead
// of returning "!!!x".
pub fn not(expr: *const Expr, allocator: std.mem.Allocator) Expr {
return expr.maybeSimplifyNot(allocator) orelse
Expr.init(
E.Unary,
E.Unary{
.op = .un_not,
.value = expr.*,
},
expr.loc,
);
}
pub inline fn hasValueForThisInCall(expr: *const Expr) bool {
return switch (expr.data) {
.e_dot, .e_index => true,
else => false,
};
}
/// The given "expr" argument should be the operand of a "!" prefix operator
/// (i.e. the "x" in "!x"). This returns a simplified expression for the
/// whole operator (i.e. the "!x") if it can be simplified, or false if not.
/// It's separate from "Not()" above to avoid allocation on failure in case
/// that is undesired.
pub fn maybeSimplifyNot(expr: *const Expr, allocator: std.mem.Allocator) ?Expr {
switch (expr.data) {
.e_null, .e_undefined => {
return expr.at(E.Boolean, E.Boolean{ .value = true }, allocator);
},
.e_boolean => |b| {
return expr.at(E.Boolean, E.Boolean{ .value = b.value }, allocator);
},
.e_number => |n| {
return expr.at(E.Boolean, E.Boolean{ .value = (n.value == 0 or std.math.isNan(n.value)) }, allocator);
},
.e_big_int => |b| {
return expr.at(E.Boolean, E.Boolean{ .value = strings.eqlComptime(b.value, "0") }, allocator);
},
.e_function,
.e_arrow,
.e_reg_exp,
=> {
return expr.at(E.Boolean, E.Boolean{ .value = false }, allocator);
},
// "!!!a" => "!a"
.e_unary => |un| {
if (un.op == Op.Code.un_not and un.value.knownPrimitive() == .boolean) {
return un.value;
}
},
.e_binary => |ex| {
// TODO: evaluate whether or not it is safe to do this mutation since it's modifying in-place.
// Make sure that these transformations are all safe for special values.
// For example, "!(a < b)" is not the same as "a >= b" if a and/or b are
// NaN (or undefined, or null, or possibly other problem cases too).
switch (ex.op) {
Op.Code.bin_loose_eq => {
// "!(a == b)" => "a != b"
ex.op = .bin_loose_ne;
return expr.*;
},
Op.Code.bin_loose_ne => {
// "!(a != b)" => "a == b"
ex.op = .bin_loose_eq;
return expr.*;
},
Op.Code.bin_strict_eq => {
// "!(a === b)" => "a !== b"
ex.op = .bin_strict_ne;
return expr.*;
},
Op.Code.bin_strict_ne => {
// "!(a !== b)" => "a === b"
ex.op = .bin_strict_eq;
return expr.*;
},
Op.Code.bin_comma => {
// "!(a, b)" => "a, !b"
ex.right = ex.right.not(allocator);
return expr.*;
},
else => {},
}
},
.e_inlined_enum => |inlined| {
return inlined.value.maybeSimplifyNot(allocator);
},
else => {},
}
return null;
}
pub fn toStringExprWithoutSideEffects(expr: *const Expr, allocator: std.mem.Allocator) ?Expr {
const unwrapped = expr.unwrapInlined();
const slice = switch (unwrapped.data) {
.e_null => "null",
.e_string => return expr.*,
.e_undefined => "undefined",
.e_boolean => |data| if (data.value) "true" else "false",
.e_big_int => |bigint| bigint.value,
.e_number => |num| if (num.toString(allocator)) |str|
str
else
null,
.e_reg_exp => |regexp| regexp.value,
.e_dot => |dot| @as(?[]const u8, brk: {
// This is dumb but some JavaScript obfuscators use this to generate string literals
if (bun.strings.eqlComptime(dot.name, "constructor")) {
break :brk switch (dot.target.data) {
.e_string => "function String() { [native code] }",
.e_reg_exp => "function RegExp() { [native code] }",
else => null,
};
}
break :brk null;
}),
else => null,
};
return if (slice) |s| Expr.init(E.String, E.String.init(s), expr.loc) else null;
}
pub fn isOptionalChain(self: *const @This()) bool {
return switch (self.data) {
.e_dot => self.data.e_dot.optional_chain != null,
.e_index => self.data.e_index.optional_chain != null,
.e_call => self.data.e_call.optional_chain != null,
else => false,
};
}
pub inline fn knownPrimitive(self: *const @This()) PrimitiveType {
return self.data.knownPrimitive();
}
pub const PrimitiveType = enum {
unknown,
mixed,
null,
undefined,
boolean,
number,
string,
bigint,
pub const static = std.enums.EnumSet(PrimitiveType).init(.{
.mixed = true,
.null = true,
.undefined = true,
.boolean = true,
.number = true,
.string = true,
// for our purposes, bigint is dynamic
// it is technically static though
// .@"bigint" = true,
});
pub inline fn isStatic(this: PrimitiveType) bool {
return static.contains(this);
}
pub fn merge(left_known: PrimitiveType, right_known: PrimitiveType) PrimitiveType {
if (right_known == .unknown or left_known == .unknown)
return .unknown;
return if (left_known == right_known)
left_known
else
.mixed;
}
};
pub const Data = union(Tag) {
e_array: *E.Array,
e_unary: *E.Unary,
e_binary: *E.Binary,
e_class: *E.Class,
e_new: *E.New,
e_function: *E.Function,
e_call: *E.Call,
e_dot: *E.Dot,
e_index: *E.Index,
e_arrow: *E.Arrow,
e_jsx_element: *E.JSXElement,
e_object: *E.Object,
e_spread: *E.Spread,
e_template: *E.Template,
e_reg_exp: *E.RegExp,
e_await: *E.Await,
e_yield: *E.Yield,
e_if: *E.If,
e_import: *E.Import,
e_identifier: E.Identifier,
e_import_identifier: E.ImportIdentifier,
e_private_identifier: E.PrivateIdentifier,
e_commonjs_export_identifier: E.CommonJSExportIdentifier,
e_boolean: E.Boolean,
e_number: E.Number,
e_big_int: *E.BigInt,
e_string: *E.String,
e_require_string: E.RequireString,
e_require_resolve_string: E.RequireResolveString,
e_require_call_target,
e_require_resolve_call_target,
e_missing: E.Missing,
e_this: E.This,
e_super: E.Super,
e_null: E.Null,
e_undefined: E.Undefined,
e_new_target: E.NewTarget,
e_import_meta: E.ImportMeta,
e_import_meta_main: E.ImportMetaMain,
e_require_main,
/// Covers some exotic AST node types under one namespace, since the
/// places this is found it all follows similar handling.
e_special: E.Special,
e_inlined_enum: *E.InlinedEnum,
e_name_of_symbol: *E.NameOfSymbol,
comptime {
bun.assert_eql(@sizeOf(Data), 24); // Do not increase the size of Expr
}
pub fn as(data: Data, comptime tag: Tag) ?@FieldType(Data, @tagName(tag)) {
return if (data == tag) @field(data, @tagName(tag)) else null;
}
pub fn clone(this: Expr.Data, allocator: std.mem.Allocator) !Data {
return switch (this) {
.e_array => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_array)));
item.* = el.*;
return .{ .e_array = item };
},
.e_unary => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_unary)));
item.* = el.*;
return .{ .e_unary = item };
},
.e_binary => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_binary)));
item.* = el.*;
return .{ .e_binary = item };
},
.e_class => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_class)));
item.* = el.*;
return .{ .e_class = item };
},
.e_new => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_new)));
item.* = el.*;
return .{ .e_new = item };
},
.e_function => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_function)));
item.* = el.*;
return .{ .e_function = item };
},
.e_call => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_call)));
item.* = el.*;
return .{ .e_call = item };
},
.e_dot => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_dot)));
item.* = el.*;
return .{ .e_dot = item };
},
.e_index => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_index)));
item.* = el.*;
return .{ .e_index = item };
},
.e_arrow => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_arrow)));
item.* = el.*;
return .{ .e_arrow = item };
},
.e_jsx_element => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_jsx_element)));
item.* = el.*;
return .{ .e_jsx_element = item };
},
.e_object => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_object)));
item.* = el.*;
return .{ .e_object = item };
},
.e_spread => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_spread)));
item.* = el.*;
return .{ .e_spread = item };
},
.e_template => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_template)));
item.* = el.*;
return .{ .e_template = item };
},
.e_reg_exp => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_reg_exp)));
item.* = el.*;
return .{ .e_reg_exp = item };
},
.e_await => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_await)));
item.* = el.*;
return .{ .e_await = item };
},
.e_yield => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_yield)));
item.* = el.*;
return .{ .e_yield = item };
},
.e_if => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_if)));
item.* = el.*;
return .{ .e_if = item };
},
.e_import => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_import)));
item.* = el.*;
return .{ .e_import = item };
},
.e_big_int => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_big_int)));
item.* = el.*;
return .{ .e_big_int = item };
},
.e_string => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_string)));
item.* = el.*;
return .{ .e_string = item };
},
.e_inlined_enum => |el| {
const item = try allocator.create(std.meta.Child(@TypeOf(this.e_inlined_enum)));
item.* = el.*;
return .{ .e_inlined_enum = item };
},
else => this,
};
}
pub fn deepClone(this: Expr.Data, allocator: std.mem.Allocator) !Data {
return switch (this) {
.e_array => |el| {
const items = try el.items.deepClone(allocator);
const item = bun.create(allocator, E.Array, .{
.items = items,
.comma_after_spread = el.comma_after_spread,
.was_originally_macro = el.was_originally_macro,
.is_single_line = el.is_single_line,
.is_parenthesized = el.is_parenthesized,
.close_bracket_loc = el.close_bracket_loc,
});
return .{ .e_array = item };
},
.e_unary => |el| {
const item = bun.create(allocator, E.Unary, .{
.op = el.op,
.value = try el.value.deepClone(allocator),
.flags = el.flags,
});
return .{ .e_unary = item };
},
.e_binary => |el| {
const item = bun.create(allocator, E.Binary, .{
.op = el.op,
.left = try el.left.deepClone(allocator),
.right = try el.right.deepClone(allocator),
});
return .{ .e_binary = item };
},
.e_class => |el| {
const properties = try allocator.alloc(G.Property, el.properties.len);
for (el.properties, 0..) |prop, i| {
properties[i] = try prop.deepClone(allocator);
}
const item = bun.create(allocator, E.Class, .{
.class_keyword = el.class_keyword,
.ts_decorators = try el.ts_decorators.deepClone(allocator),
.class_name = el.class_name,
.extends = if (el.extends) |e| try e.deepClone(allocator) else null,
.body_loc = el.body_loc,
.close_brace_loc = el.close_brace_loc,
.properties = properties,
.has_decorators = el.has_decorators,
});
return .{ .e_class = item };
},
.e_new => |el| {
const item = bun.create(allocator, E.New, .{
.target = try el.target.deepClone(allocator),
.args = try el.args.deepClone(allocator),
.can_be_unwrapped_if_unused = el.can_be_unwrapped_if_unused,
.close_parens_loc = el.close_parens_loc,
});
return .{ .e_new = item };
},
.e_function => |el| {
const item = bun.create(allocator, E.Function, .{
.func = try el.func.deepClone(allocator),
});
return .{ .e_function = item };
},
.e_call => |el| {
const item = bun.create(allocator, E.Call, .{
.target = try el.target.deepClone(allocator),
.args = try el.args.deepClone(allocator),
.optional_chain = el.optional_chain,
.is_direct_eval = el.is_direct_eval,
.close_paren_loc = el.close_paren_loc,
.can_be_unwrapped_if_unused = el.can_be_unwrapped_if_unused,
.was_jsx_element = el.was_jsx_element,
});
return .{ .e_call = item };
},
.e_dot => |el| {
const item = bun.create(allocator, E.Dot, .{
.target = try el.target.deepClone(allocator),
.name = el.name,
.name_loc = el.name_loc,
.optional_chain = el.optional_chain,
.can_be_removed_if_unused = el.can_be_removed_if_unused,
.call_can_be_unwrapped_if_unused = el.call_can_be_unwrapped_if_unused,
});
return .{ .e_dot = item };
},
.e_index => |el| {
const item = bun.create(allocator, E.Index, .{
.target = try el.target.deepClone(allocator),
.index = try el.index.deepClone(allocator),
.optional_chain = el.optional_chain,
});
return .{ .e_index = item };
},
.e_arrow => |el| {
const args = try allocator.alloc(G.Arg, el.args.len);
for (0..args.len) |i| {
args[i] = try el.args[i].deepClone(allocator);
}
const item = bun.create(allocator, E.Arrow, .{
.args = args,
.body = el.body,
.is_async = el.is_async,
.has_rest_arg = el.has_rest_arg,
.prefer_expr = el.prefer_expr,
});
return .{ .e_arrow = item };
},
.e_jsx_element => |el| {
const item = bun.create(allocator, E.JSXElement, .{
.tag = if (el.tag) |tag| try tag.deepClone(allocator) else null,
.properties = try el.properties.deepClone(allocator),
.children = try el.children.deepClone(allocator),
.key_prop_index = el.key_prop_index,
.flags = el.flags,
.close_tag_loc = el.close_tag_loc,
});
return .{ .e_jsx_element = item };
},
.e_object => |el| {
const item = bun.create(allocator, E.Object, .{
.properties = try el.properties.deepClone(allocator),
.comma_after_spread = el.comma_after_spread,
.is_single_line = el.is_single_line,
.is_parenthesized = el.is_parenthesized,
.was_originally_macro = el.was_originally_macro,
.close_brace_loc = el.close_brace_loc,
});
return .{ .e_object = item };
},
.e_spread => |el| {
const item = bun.create(allocator, E.Spread, .{
.value = try el.value.deepClone(allocator),
});
return .{ .e_spread = item };
},
.e_template => |el| {
const item = bun.create(allocator, E.Template, .{
.tag = if (el.tag) |tag| try tag.deepClone(allocator) else null,
.parts = el.parts,
.head = el.head,
});
return .{ .e_template = item };
},
.e_reg_exp => |el| {
const item = bun.create(allocator, E.RegExp, .{
.value = el.value,
.flags_offset = el.flags_offset,
});
return .{ .e_reg_exp = item };
},
.e_await => |el| {
const item = bun.create(allocator, E.Await, .{
.value = try el.value.deepClone(allocator),
});
return .{ .e_await = item };
},
.e_yield => |el| {
const item = bun.create(allocator, E.Yield, .{
.value = if (el.value) |value| try value.deepClone(allocator) else null,
.is_star = el.is_star,
});
return .{ .e_yield = item };
},
.e_if => |el| {
const item = bun.create(allocator, E.If, .{
.test_ = try el.test_.deepClone(allocator),
.yes = try el.yes.deepClone(allocator),
.no = try el.no.deepClone(allocator),
});
return .{ .e_if = item };
},
.e_import => |el| {
const item = bun.create(allocator, E.Import, .{
.expr = try el.expr.deepClone(allocator),
.options = try el.options.deepClone(allocator),
.import_record_index = el.import_record_index,
});
return .{ .e_import = item };
},
.e_big_int => |el| {
const item = bun.create(allocator, E.BigInt, .{
.value = el.value,
});
return .{ .e_big_int = item };
},
.e_string => |el| {
const item = bun.create(allocator, E.String, .{
.data = el.data,
.prefer_template = el.prefer_template,
.next = el.next,
.end = el.end,
.rope_len = el.rope_len,
.is_utf16 = el.is_utf16,
});
return .{ .e_string = item };
},
.e_inlined_enum => |el| {
const item = bun.create(allocator, E.InlinedEnum, .{
.value = el.value,
.comment = el.comment,
});
return .{ .e_inlined_enum = item };
},
else => this,
};
}
/// `hasher` should be something with 'pub fn update([]const u8) void';
/// symbol table is passed to serialize `Ref` as an identifier names instead of a nondeterministic numbers
pub fn writeToHasher(this: Expr.Data, hasher: anytype, symbol_table: anytype) void {
writeAnyToHasher(hasher, std.meta.activeTag(this));
switch (this) {
.e_name_of_symbol => |e| {
const symbol = e.ref.getSymbol(symbol_table);
hasher.update(symbol.original_name);
},
.e_array => |e| {
writeAnyToHasher(hasher, .{
e.is_single_line,
e.is_parenthesized,
e.was_originally_macro,
e.items.len,
});
for (e.items.slice()) |item| {
item.data.writeToHasher(hasher, symbol_table);
}
},
.e_unary => |e| {
writeAnyToHasher(hasher, @as(u8, @bitCast(e.flags)));
writeAnyToHasher(hasher, .{e.op});
e.value.data.writeToHasher(hasher, symbol_table);
},
.e_binary => |e| {
writeAnyToHasher(hasher, .{e.op});
e.left.data.writeToHasher(hasher, symbol_table);
e.right.data.writeToHasher(hasher, symbol_table);
},
.e_class => {},
inline .e_new, .e_call => {},
.e_function => {},
.e_dot => |e| {
writeAnyToHasher(hasher, .{ e.optional_chain, e.name.len });
e.target.data.writeToHasher(hasher, symbol_table);
hasher.update(e.name);
},
.e_index => |e| {
writeAnyToHasher(hasher, .{e.optional_chain});
e.target.data.writeToHasher(hasher, symbol_table);
e.index.data.writeToHasher(hasher, symbol_table);
},
.e_arrow => {},
.e_jsx_element => |e| {
_ = e; // autofix
},
.e_object => |e| {
_ = e; // autofix
},
inline .e_spread, .e_await => |e| {
e.value.data.writeToHasher(hasher, symbol_table);
},
.e_yield => |e| {
writeAnyToHasher(hasher, .{ e.is_star, e.value });
if (e.value) |value|
value.data.writeToHasher(hasher, symbol_table);
},
.e_template => |e| {
_ = e; // autofix
},
.e_if => |e| {
_ = e; // autofix
},
.e_import => |e| {
_ = e; // autofix
},
inline .e_identifier,
.e_import_identifier,
.e_private_identifier,
.e_commonjs_export_identifier,
=> |e| {
const symbol = e.ref.getSymbol(symbol_table);
hasher.update(symbol.original_name);
},
inline .e_boolean, .e_number => |e| {
writeAnyToHasher(hasher, e.value);
},
inline .e_big_int, .e_reg_exp => |e| {
hasher.update(e.value);
},
.e_string => |e| {
var next: ?*E.String = e;
if (next) |current| {
if (current.isUTF8()) {
hasher.update(current.data);
} else {
hasher.update(bun.reinterpretSlice(u8, current.slice16()));
}
next = current.next;
hasher.update("\x00");
}
},
inline .e_require_string, .e_require_resolve_string => |e| {
writeAnyToHasher(hasher, e.import_record_index); // preferably, i'd like to write the filepath
},
.e_import_meta_main => |e| {
writeAnyToHasher(hasher, e.inverted);
},
.e_inlined_enum => |e| {
// pretend there is no comment
e.value.data.writeToHasher(hasher, symbol_table);
},
// no data
.e_require_call_target,
.e_require_resolve_call_target,
.e_missing,
.e_this,
.e_super,
.e_null,
.e_undefined,
.e_new_target,
.e_require_main,
.e_import_meta,
.e_special,
=> {},
}
}
/// "const values" here refers to expressions that can participate in constant
/// inlining, as they have no side effects on instantiation, and there would be
/// no observable difference if duplicated. This is a subset of canBeMoved()
pub fn canBeConstValue(this: Expr.Data) bool {
return switch (this) {
.e_number,
.e_boolean,
.e_null,
.e_undefined,
.e_inlined_enum,
=> true,
.e_string => |str| str.next == null,
.e_array => |array| array.was_originally_macro,
.e_object => |object| object.was_originally_macro,
else => false,
};
}
/// Expressions that can be moved are those that do not have side
/// effects on their own. This is used to determine what can be moved
/// outside of a module wrapper (__esm/__commonJS).
pub fn canBeMoved(data: Expr.Data) bool {
return switch (data) {
// TODO: identifiers can be removed if unused, however code that
// moves expressions around sometimes does so incorrectly when
// doing destructures. test case: https://github.com/oven-sh/bun/issues/14027
// .e_identifier => |id| id.can_be_removed_if_unused,
.e_class => |class| class.canBeMoved(),
.e_arrow,
.e_function,
.e_number,
.e_boolean,
.e_null,
.e_undefined,
// .e_reg_exp,
.e_big_int,
.e_string,
.e_inlined_enum,
.e_import_meta,
=> true,
.e_template => |template| template.tag == null and template.parts.len == 0,
.e_array => |array| array.was_originally_macro,
.e_object => |object| object.was_originally_macro,
// TODO: experiment with allowing some e_binary, e_unary, e_if as movable
else => false,
};
}
pub fn isSafeToString(data: Expr.Data) bool {
return switch (data) {
// rope strings can throw when toString is called.
.e_string => |str| str.next == null,
.e_number, .e_boolean, .e_undefined, .e_null => true,
// BigInt is deliberately excluded as a large enough BigInt could throw an out of memory error.
//
else => false,
};
}
pub fn knownPrimitive(data: Expr.Data) PrimitiveType {
return switch (data) {
.e_big_int => .bigint,
.e_boolean => .boolean,
.e_null => .null,
.e_number => .number,
.e_string => .string,
.e_undefined => .undefined,
.e_template => if (data.e_template.tag == null) PrimitiveType.string else PrimitiveType.unknown,
.e_if => mergeKnownPrimitive(data.e_if.yes.data, data.e_if.no.data),
.e_binary => |binary| brk: {
switch (binary.op) {
.bin_strict_eq,
.bin_strict_ne,
.bin_loose_eq,
.bin_loose_ne,
.bin_lt,
.bin_gt,
.bin_le,
.bin_ge,
.bin_instanceof,
.bin_in,
=> break :brk PrimitiveType.boolean,
.bin_logical_or, .bin_logical_and => break :brk binary.left.data.mergeKnownPrimitive(binary.right.data),
.bin_nullish_coalescing => {
const left = binary.left.data.knownPrimitive();
const right = binary.right.data.knownPrimitive();
if (left == .null or left == .undefined)
break :brk right;
if (left != .unknown) {
if (left != .mixed)
break :brk left; // Definitely not null or undefined
if (right != .unknown)
break :brk PrimitiveType.mixed; // Definitely some kind of primitive
}
},
.bin_add => {
const left = binary.left.data.knownPrimitive();
const right = binary.right.data.knownPrimitive();
if (left == .string or right == .string)
break :brk PrimitiveType.string;
if (left == .bigint or right == .bigint)
break :brk PrimitiveType.bigint;
if (switch (left) {
.unknown, .mixed, .bigint => false,
else => true,
} and switch (right) {
.unknown, .mixed, .bigint => false,
else => true,
})
break :brk PrimitiveType.number;
break :brk PrimitiveType.mixed; // Can be number or bigint or string (or an exception)
},
.bin_sub,
.bin_sub_assign,
.bin_mul,
.bin_mul_assign,
.bin_div,
.bin_div_assign,
.bin_rem,
.bin_rem_assign,
.bin_pow,
.bin_pow_assign,
.bin_bitwise_and,
.bin_bitwise_and_assign,
.bin_bitwise_or,
.bin_bitwise_or_assign,
.bin_bitwise_xor,
.bin_bitwise_xor_assign,
.bin_shl,
.bin_shl_assign,
.bin_shr,
.bin_shr_assign,
.bin_u_shr,
.bin_u_shr_assign,
=> break :brk PrimitiveType.mixed, // Can be number or bigint (or an exception)
.bin_assign,
.bin_comma,
=> break :brk binary.right.data.knownPrimitive(),
else => {},
}
break :brk PrimitiveType.unknown;
},
.e_unary => switch (data.e_unary.op) {
.un_void => PrimitiveType.undefined,
.un_typeof => PrimitiveType.string,
.un_not, .un_delete => PrimitiveType.boolean,
.un_pos => PrimitiveType.number, // Cannot be bigint because that throws an exception
.un_neg, .un_cpl => switch (data.e_unary.value.data.knownPrimitive()) {
.bigint => PrimitiveType.bigint,
.unknown, .mixed => PrimitiveType.mixed,
else => PrimitiveType.number, // Can be number or bigint
},
.un_pre_dec, .un_pre_inc, .un_post_dec, .un_post_inc => PrimitiveType.mixed, // Can be number or bigint
else => PrimitiveType.unknown,
},
.e_inlined_enum => |inlined| inlined.value.data.knownPrimitive(),
else => PrimitiveType.unknown,
};
}
pub fn mergeKnownPrimitive(lhs: Expr.Data, rhs: Expr.Data) PrimitiveType {
return lhs.knownPrimitive().merge(rhs.knownPrimitive());
}
/// Returns true if the result of the "typeof" operator on this expression is
/// statically determined and this expression has no side effects (i.e. can be
/// removed without consequence).
pub inline fn toTypeof(data: Expr.Data) ?string {
return @as(Expr.Tag, data).typeof();
}
pub fn toNumber(data: Expr.Data) ?f64 {
return switch (data) {
.e_null => 0,
.e_undefined => std.math.nan(f64),
.e_string => |str| {
if (str.next != null) return null;
if (!str.isUTF8()) return null;
// +'1' => 1
return stringToEquivalentNumberValue(str.slice8());
},
.e_boolean => @as(f64, if (data.e_boolean.value) 1.0 else 0.0),
.e_number => data.e_number.value,
.e_inlined_enum => |inlined| switch (inlined.value.data) {
.e_number => |num| num.value,
.e_string => |str| {
if (str.next != null) return null;
if (!str.isUTF8()) return null;
// +'1' => 1
return stringToEquivalentNumberValue(str.slice8());
},
else => null,
},
else => null,
};
}
pub fn toFiniteNumber(data: Expr.Data) ?f64 {
return switch (data) {
.e_boolean => @as(f64, if (data.e_boolean.value) 1.0 else 0.0),
.e_number => if (std.math.isFinite(data.e_number.value))
data.e_number.value
else
null,
.e_inlined_enum => |inlined| switch (inlined.value.data) {
.e_number => |num| if (std.math.isFinite(num.value))
num.value
else
null,
else => null,
},
else => null,
};
}
pub fn extractNumericValue(data: Expr.Data) ?f64 {
return switch (data) {
.e_number => data.e_number.value,
.e_inlined_enum => |inlined| switch (inlined.value.data) {
.e_number => |num| num.value,
else => null,
},
else => null,
};
}
pub fn extractStringValue(data: Expr.Data) ?*E.String {
return switch (data) {
.e_string => data.e_string,
.e_inlined_enum => |inlined| switch (inlined.value.data) {
.e_string => |str| str,
else => null,
},
else => null,
};
}
pub const Equality = struct {
equal: bool = false,
ok: bool = false,
/// This extra flag is unfortunately required for the case of visiting the expression
/// `require.main === module` (and any combination of !==, ==, !=, either ordering)
///
/// We want to replace this with the dedicated import_meta_main node, which:
/// - Stops this module from having p.require_ref, allowing conversion to ESM
/// - Allows us to inline `import.meta.main`'s value, if it is known (bun build --compile)
is_require_main_and_module: bool = false,
pub const @"true" = Equality{ .ok = true, .equal = true };
pub const @"false" = Equality{ .ok = true, .equal = false };
pub const unknown = Equality{ .ok = false };
};
// Returns "equal, ok". If "ok" is false, then nothing is known about the two
// values. If "ok" is true, the equality or inequality of the two values is
// stored in "equal".
pub fn eql(
left: Expr.Data,
right: Expr.Data,
p: anytype,
comptime kind: enum { loose, strict },
) Equality {
comptime bun.assert(@typeInfo(@TypeOf(p)).pointer.size == .one); // pass *Parser
// https://dorey.github.io/JavaScript-Equality-Table/
switch (left) {
.e_inlined_enum => |inlined| return inlined.value.data.eql(right, p, kind),
.e_null, .e_undefined => {
const ok = switch (@as(Expr.Tag, right)) {
.e_null, .e_undefined => true,
else => @as(Expr.Tag, right).isPrimitiveLiteral(),
};
if (comptime kind == .loose) {
return .{
.equal = switch (@as(Expr.Tag, right)) {
.e_null, .e_undefined => true,
else => false,
},
.ok = ok,
};
}
return .{
.equal = @as(Tag, right) == @as(Tag, left),
.ok = ok,
};
},
.e_boolean => |l| {
switch (right) {
.e_boolean => {
return .{
.ok = true,
.equal = l.value == right.e_boolean.value,
};
},
.e_number => |num| {
if (comptime kind == .strict) {
// "true === 1" is false
// "false === 0" is false
return Equality.false;
}
return .{
.ok = true,
.equal = if (l.value)
num.value == 1
else
num.value == 0,
};
},
.e_null, .e_undefined => {
return Equality.false;
},
else => {},
}
},
.e_number => |l| {
switch (right) {
.e_number => |r| {
return .{
.ok = true,
.equal = l.value == r.value,
};
},
.e_inlined_enum => |r| if (r.value.data == .e_number) {
return .{
.ok = true,
.equal = l.value == r.value.data.e_number.value,
};
},
.e_boolean => |r| {
if (comptime kind == .loose) {
return .{
.ok = true,
// "1 == true" is true
// "0 == false" is true
.equal = if (r.value)
l.value == 1
else
l.value == 0,
};
}
// "1 === true" is false
// "0 === false" is false
return Equality.false;
},
.e_null, .e_undefined => {
// "(not null or undefined) == undefined" is false
return Equality.false;
},
else => {},
}
},
.e_big_int => |l| {
if (right == .e_big_int) {
if (strings.eqlLong(l.value, right.e_big_int.value, true)) {
return Equality.true;
}
// 0x0000n == 0n is true
return .{ .ok = false };
} else {
return .{
.ok = switch (right) {
.e_null, .e_undefined => true,
else => false,
},
.equal = false,
};
}
},
.e_string => |l| {
switch (right) {
.e_string => |r| {
r.resolveRopeIfNeeded(p.allocator);
l.resolveRopeIfNeeded(p.allocator);
return .{
.ok = true,
.equal = r.eql(E.String, l),
};
},
.e_inlined_enum => |inlined| {
if (inlined.value.data == .e_string) {
const r = inlined.value.data.e_string;
r.resolveRopeIfNeeded(p.allocator);
l.resolveRopeIfNeeded(p.allocator);
return .{
.ok = true,
.equal = r.eql(E.String, l),
};
}
},
.e_null, .e_undefined => {
return Equality.false;
},
.e_number => |r| {
if (comptime kind == .loose) {
l.resolveRopeIfNeeded(p.allocator);
if (r.value == 0 and (l.isBlank() or l.eqlComptime("0"))) {
return Equality.true;
}
if (r.value == 1 and l.eqlComptime("1")) {
return Equality.true;
}
// the string could still equal 0 or 1 but it could be hex, binary, octal, ...
return Equality.unknown;
} else {
return Equality.false;
}
},
else => {},
}
},
else => {
// Do not need to check left because e_require_main is
// always re-ordered to the right side.
if (right == .e_require_main) {
if (left.as(.e_identifier)) |id| {
if (id.ref.eql(p.module_ref)) return .{
.ok = true,
.equal = true,
.is_require_main_and_module = true,
};
}
}
},
}
return Equality.unknown;
}
pub fn toJS(this: Data, allocator: std.mem.Allocator, globalObject: *jsc.JSGlobalObject) ToJSError!jsc.JSValue {
return switch (this) {
.e_array => |e| e.toJS(allocator, globalObject),
.e_object => |e| e.toJS(allocator, globalObject),
.e_string => |e| e.toJS(allocator, globalObject),
.e_null => jsc.JSValue.null,
.e_undefined => .js_undefined,
.e_boolean => |boolean| if (boolean.value)
.true
else
.false,
.e_number => |e| e.toJS(),
// .e_big_int => |e| e.toJS(ctx, exception),
.e_inlined_enum => |inlined| inlined.value.data.toJS(allocator, globalObject),
.e_identifier,
.e_import_identifier,
.e_private_identifier,
.e_commonjs_export_identifier,
=> error.@"Cannot convert identifier to JS. Try a statically-known value",
// brk: {
// // var node = try allocator.create(Macro.JSNode);
// // node.* = Macro.JSNode.initExpr(Expr{ .data = this, .loc = logger.Loc.Empty });
// // break :brk jsc.JSValue.c(Macro.JSNode.Class.make(globalObject, node));
// },
else => {
return error.@"Cannot convert argument type to JS";
},
};
}
pub const Store = struct {
const StoreType = NewStore(&.{
E.NameOfSymbol,
E.Array,
E.Arrow,
E.Await,
E.BigInt,
E.Binary,
E.Call,
E.Class,
E.Dot,
E.Function,
E.If,
E.Import,
E.Index,
E.InlinedEnum,
E.JSXElement,
E.New,
E.Number,
E.Object,
E.PrivateIdentifier,
E.RegExp,
E.Spread,
E.String,
E.Template,
E.TemplatePart,
E.Unary,
E.Yield,
}, 512);
pub threadlocal var instance: ?*StoreType = null;
pub threadlocal var memory_allocator: ?*ASTMemoryAllocator = null;
pub threadlocal var disable_reset = false;
pub fn create() void {
if (instance != null or memory_allocator != null) {
return;
}
instance = StoreType.init();
}
pub fn reset() void {
if (disable_reset or memory_allocator != null) return;
instance.?.reset();
}
pub fn deinit() void {
if (instance == null or memory_allocator != null) return;
instance.?.deinit();
instance = null;
}
pub inline fn assert() void {
if (comptime Environment.isDebug or Environment.enable_asan) {
if (instance == null and memory_allocator == null)
bun.unreachablePanic("Store must be init'd", .{});
}
}
/// create || reset
pub fn begin() void {
if (memory_allocator != null) return;
if (instance == null) {
create();
return;
}
if (!disable_reset)
instance.?.reset();
}
pub fn append(comptime T: type, value: T) *T {
if (memory_allocator) |allocator| {
return allocator.append(T, value);
}
Disabler.assert();
return instance.?.append(T, value);
}
};
pub inline fn isStringValue(self: Data) bool {
return @as(Expr.Tag, self) == .e_string;
}
};
pub fn StoredData(tag: Tag) type {
const T = @FieldType(Data, tag);
return switch (@typeInfo(T)) {
.pointer => |ptr| ptr.child,
else => T,
};
}
fn stringToEquivalentNumberValue(str: []const u8) f64 {
// +"" -> 0
if (str.len == 0) return 0;
if (!bun.strings.isAllASCII(str))
return std.math.nan(f64);
return bun.cpp.JSC__jsToNumber(str.ptr, str.len);
}
const string = []const u8;
const stringZ = [:0]const u8;
const JSPrinter = @import("../js_printer.zig");
const std = @import("std");
const bun = @import("bun");
const Environment = bun.Environment;
const JSONParser = bun.json;
const MutableString = bun.MutableString;
const OOM = bun.OOM;
const default_allocator = bun.default_allocator;
const jsc = bun.jsc;
const logger = bun.logger;
const strings = bun.strings;
const writeAnyToHasher = bun.writeAnyToHasher;
const MimeType = bun.http.MimeType;
const js_ast = bun.ast;
const ASTMemoryAllocator = js_ast.ASTMemoryAllocator;
const E = js_ast.E;
const Expr = js_ast.Expr;
const G = js_ast.G;
const NewStore = js_ast.NewStore;
const Op = js_ast.Op;
const Ref = js_ast.Ref;
const S = js_ast.S;
const Stmt = js_ast.Stmt;
const ToJSError = js_ast.ToJSError;