Compare commits

...

8 Commits

Author SHA1 Message Date
Jarred Sumner
e8ea8ebc7d Update js_parser.zig 2025-07-05 00:29:20 -07:00
Jarred Sumner
92e96d5e49 Update js_parser.zig 2025-07-05 00:14:21 -07:00
Jarred Sumner
806c173dec Slightly shrink stack size 2025-07-05 00:14:17 -07:00
Jarred Sumner
4753eb0fa9 Split up parseSuffix 2025-07-04 23:51:50 -07:00
Jarred Sumner
47bc72a255 Fix macro regression + be more clever about stack space usage 2025-07-04 21:48:47 -07:00
Jarred Sumner
b2d3e456ab Add error return trace for js parser errors 2025-07-04 21:47:04 -07:00
Jarred Sumner
57282893dc Remove duplicate, unnecessary implementations in comptime_string_map and reduce stack space usage 2025-07-04 21:46:45 -07:00
Jarred Sumner
c2ca3a1e13 Shrink Expr from 32 bytes -> 24 bytes 2025-07-04 18:49:38 -07:00
11 changed files with 1323 additions and 1005 deletions

View File

@@ -238,29 +238,56 @@ pub const Function = struct { func: G.Fn };
pub const Identifier = struct {
ref: Ref = Ref.None,
// If we're inside a "with" statement, this identifier may be a property
// access. In that case it would be incorrect to remove this identifier since
// the property access may be a getter or setter with side effects.
must_keep_due_to_with_stmt: bool = false,
/// If we're inside a "with" statement, this identifier may be a property
/// access. In that case it would be incorrect to remove this identifier since
/// the property access may be a getter or setter with side effects.
pub inline fn must_keep_due_to_with_stmt(this: Identifier) bool {
return this.ref.flags.flag1;
}
pub inline fn setMustKeepDueToWithStmt(this: *Identifier, value: bool) void {
this.ref.flags.flag1 = value;
}
// If true, this identifier is known to not have a side effect (i.e. to not
// throw an exception) when referenced. If false, this identifier may or
// not have side effects when referenced. This is used to allow the removal
// of known globals such as "Object" if they aren't used.
can_be_removed_if_unused: bool = false,
pub inline fn can_be_removed_if_unused(this: Identifier) bool {
return this.ref.flags.flag2;
}
pub inline fn setCanBeRemovedIfUnused(this: *Identifier, value: bool) void {
this.ref.flags.flag2 = value;
}
// If true, this identifier represents a function that, when called, can be
// unwrapped if the resulting value is unused. Unwrapping means discarding
// the call target but keeping any arguments with side effects.
call_can_be_unwrapped_if_unused: bool = false,
pub inline fn call_can_be_unwrapped_if_unused(this: Identifier) bool {
return this.ref.flags.flag3;
}
pub inline fn init(ref: Ref) Identifier {
return Identifier{
.ref = ref,
.must_keep_due_to_with_stmt = false,
.can_be_removed_if_unused = false,
.call_can_be_unwrapped_if_unused = false,
pub inline fn setCallCanBeUnwrappedIfUnused(this: *Identifier, value: bool) void {
this.ref.flags.flag3 = value;
}
pub const Options = struct {
ref: Ref,
must_keep_due_to_with_stmt: bool = false,
can_be_removed_if_unused: bool = false,
call_can_be_unwrapped_if_unused: bool = false,
};
pub fn init(options: Options) Identifier {
var this = Identifier{
.ref = options.ref.withoutFlags(),
};
this.ref.flags.flag1 = options.must_keep_due_to_with_stmt;
this.ref.flags.flag2 = options.can_be_removed_if_unused;
this.ref.flags.flag3 = options.call_can_be_unwrapped_if_unused;
return this;
}
};
@@ -289,7 +316,22 @@ pub const ImportIdentifier = struct {
/// If true, this was originally an identifier expression such as "foo". If
/// false, this could potentially have been a member access expression such
/// as "ns.foo" off of an imported namespace object.
was_originally_identifier: bool = false,
pub fn was_originally_identifier(this: ImportIdentifier) bool {
return this.ref.flags.flag1;
}
pub const Options = struct {
ref: Ref,
was_originally_identifier: bool = false,
};
pub fn init(options: Options) ImportIdentifier {
var this = ImportIdentifier{
.ref = options.ref.withoutFlags(),
};
this.ref.flags.flag1 = options.was_originally_identifier;
return this;
}
};
/// This is a dot expression on exports, such as `exports.<ref>`. It is given
@@ -297,7 +339,31 @@ pub const ImportIdentifier = struct {
/// the identifier in the Ref
pub const CommonJSExportIdentifier = struct {
ref: Ref = Ref.None,
base: Base = .exports,
pub inline fn base(this: CommonJSExportIdentifier) Base {
if (this.ref.flags.flag1) {
return .module_dot_exports;
}
return .exports;
}
pub inline fn setBase(this: *CommonJSExportIdentifier, base_: Base) void {
this.ref.flags.flag1 = base_ == .module_dot_exports;
}
pub const Options = struct {
ref: Ref,
base: Base = .exports,
};
pub inline fn init(options: Options) CommonJSExportIdentifier {
var this = CommonJSExportIdentifier{
.ref = options.ref.withoutFlags(),
};
this.setBase(options.base);
return this;
}
/// The original variant of the dot expression must be known so that in the case that we
/// - fail to convert this to ESM

View File

@@ -118,7 +118,9 @@ pub inline fn initIdentifier(ref: Ref, loc: logger.Loc) Expr {
return Expr{
.loc = loc,
.data = .{
.e_identifier = E.Identifier.init(ref),
.e_identifier = E.Identifier.init(.{
.ref = ref,
}),
},
};
}
@@ -849,33 +851,36 @@ pub fn allocate(allocator: std.mem.Allocator, comptime Type: type, st: Type, loc
return Expr{
.loc = loc,
.data = Data{
.e_identifier = 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 => {
E.Identifier.Options => {
return Expr{
.loc = loc,
.data = Data{
.e_import_identifier = .{
.e_identifier = E.Identifier.init(st),
},
};
},
E.ImportIdentifier.Options => {
return Expr{
.loc = loc,
.data = Data{
.e_import_identifier = E.ImportIdentifier.init(.{
.ref = st.ref,
.was_originally_identifier = st.was_originally_identifier,
},
}),
},
};
},
E.CommonJSExportIdentifier => {
E.CommonJSExportIdentifier.Options => {
return Expr{
.loc = loc,
.data = Data{
.e_commonjs_export_identifier = .{
.ref = st.ref,
},
.e_commonjs_export_identifier = E.CommonJSExportIdentifier.init(st),
},
};
},
@@ -1224,26 +1229,46 @@ pub fn init(comptime Type: type, st: Type, loc: logger.Loc) Expr {
},
};
},
E.Identifier.Options => {
return Expr{
.loc = loc,
.data = Data{
.e_identifier = E.Identifier.init(.{
.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.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.Options => {
return Expr{
.loc = loc,
.data = Data{
.e_import_identifier = E.ImportIdentifier.init(.{
.ref = st.ref,
.was_originally_identifier = st.was_originally_identifier,
}),
},
};
},
E.ImportIdentifier => {
return Expr{
.loc = loc,
.data = Data{
.e_import_identifier = .{
.e_import_identifier = E.ImportIdentifier{
.ref = st.ref,
.was_originally_identifier = st.was_originally_identifier,
},
},
};
@@ -1252,13 +1277,20 @@ pub fn init(comptime Type: type, st: Type, loc: logger.Loc) Expr {
return Expr{
.loc = loc,
.data = Data{
.e_commonjs_export_identifier = .{
.e_commonjs_export_identifier = E.CommonJSExportIdentifier{
.ref = st.ref,
.base = st.base,
},
},
};
},
E.CommonJSExportIdentifier.Options => {
return Expr{
.loc = loc,
.data = Data{
.e_commonjs_export_identifier = E.CommonJSExportIdentifier.init(st),
},
};
},
E.PrivateIdentifier => {
return Expr{
.loc = loc,
@@ -2153,7 +2185,7 @@ pub const Data = union(Tag) {
e_name_of_symbol: *E.NameOfSymbol,
comptime {
bun.assert_eql(@sizeOf(Data), 24); // Do not increase the size of Expr
bun.assert_eql(@sizeOf(Data), 16); // Do not increase the size of Expr
}
pub fn as(data: Data, comptime tag: Tag) ?@FieldType(Data, @tagName(tag)) {
@@ -3180,6 +3212,10 @@ pub const Data = union(Tag) {
}
};
comptime {
bun.assert_eql(@sizeOf(Expr), 24); // do not make Expr bigger.
}
pub fn StoredData(tag: Tag) type {
const T = @FieldType(Data, tag);
return switch (@typeInfo(T)) {

View File

@@ -103,6 +103,7 @@ pub const Index = packed struct(u32) {
/// The maps can be merged quickly by creating a single outer array containing
/// all inner arrays from all parsed files.
pub const Ref = packed struct(u64) {
pub const SourceIndex = u28;
pub const Int = u31;
inner_index: Int = 0,
@@ -113,8 +114,19 @@ pub const Ref = packed struct(u64) {
source_contents_slice,
symbol,
},
/// These flags are used by:
///
/// - E.ImportIdentifier
/// - E.Identifier
/// - E.CommonJSExportIdentifier
///
flags: packed struct(u3) {
flag1: bool = false,
flag2: bool = false,
flag3: bool = false,
} = .{},
source_index: Int = 0,
source_index: SourceIndex = 0,
/// Represents a null state without using an extra bit
pub const None = Ref{ .inner_index = 0, .source_index = 0, .tag = .invalid };
@@ -131,13 +143,22 @@ pub const Ref = packed struct(u64) {
pub const HashCtx = RefCtx;
pub fn isSourceIndexNull(this: anytype) bool {
return this == std.math.maxInt(Int);
return this == std.math.maxInt(SourceIndex);
}
pub fn isSymbol(this: Ref) bool {
return this.tag == .symbol;
}
pub inline fn withoutFlags(this: Ref) Ref {
return .{
.inner_index = this.inner_index,
.source_index = this.source_index,
.tag = this.tag,
.flags = .{},
};
}
pub fn format(ref: Ref, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try std.fmt.format(
writer,
@@ -175,7 +196,7 @@ pub const Ref = packed struct(u64) {
return this.tag != .invalid;
}
pub inline fn sourceIndex(this: Ref) Int {
pub inline fn sourceIndex(this: Ref) SourceIndex {
return this.source_index;
}
@@ -205,7 +226,7 @@ pub const Ref = packed struct(u64) {
}
pub inline fn asU64(key: Ref) u64 {
return @bitCast(key);
return @bitCast(key.withoutFlags());
}
pub inline fn hash64(key: Ref) u64 {

View File

@@ -7,7 +7,7 @@
pub const AstBuilder = struct {
allocator: std.mem.Allocator,
source: *const Logger.Source,
source_index: u31,
source_index: Ref.SourceIndex,
stmts: std.ArrayListUnmanaged(Stmt),
scopes: std.ArrayListUnmanaged(*Scope),
symbols: std.ArrayListUnmanaged(Symbol),
@@ -98,7 +98,7 @@ pub const AstBuilder = struct {
});
const ref: Ref = .{
.inner_index = inner_index,
.source_index = p.source_index,
.source_index = @intCast(p.source_index),
.tag = .symbol,
};
try p.current_scope.generated.push(p.allocator, ref);

View File

@@ -398,9 +398,10 @@ pub fn convertStmtsForChunk(
E.Binary{
.op = .bin_assign,
.left = Expr.init(
E.CommonJSExportIdentifier,
E.CommonJSExportIdentifier{
E.CommonJSExportIdentifier.Options,
.{
.ref = decl.binding.data.b_identifier.ref,
.base = .exports,
},
decl.binding.loc,
),

View File

@@ -265,6 +265,12 @@ pub const JavaScript = struct {
};
const result = parser.parse() catch |err| {
if (comptime Environment.isDebug) {
if (bun.getenvTruthy("BUN_DEBUG_PARSE_ERRORS")) {
bun.handleErrorReturnTrace(err, @errorReturnTrace());
}
}
if (temp_log.errors == 0) {
log.addRangeError(source, parser.lexer.range(), @errorName(err)) catch unreachable;
}

View File

@@ -111,8 +111,9 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
return null;
}
pub fn getWithLengthAndEql(str: anytype, comptime len: usize, comptime eqls: anytype) ?V {
const end = comptime brk: {
// Let comptime memoize the function
fn endLen(comptime len: usize) usize {
return comptime brk: {
var i = len_indexes[len];
@setEvalBranchQuota(99999);
@@ -120,10 +121,13 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
break :brk i;
};
}
pub fn getWithLengthAndEql(str: anytype, comptime len: usize, comptime eqls: anytype) ?V {
// This benchmarked faster for both small and large lists of strings than using a big switch statement
// But only so long as the keys are a sorted list.
inline for (len_indexes[len]..end) |i| {
inline for (len_indexes[len]..comptime endLen(len)) |i| {
if (eqls(str, kvs[i].key)) {
return kvs[i].value;
}
@@ -133,14 +137,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
}
pub fn getWithLengthAndEqlList(str: anytype, comptime len: usize, comptime eqls: anytype) ?V {
const end = comptime brk: {
var i = len_indexes[len];
@setEvalBranchQuota(99999);
while (i < kvs.len and kvs[i].key.len == len) : (i += 1) {}
break :brk i;
};
const end = comptime endLen(len);
const start = comptime len_indexes[len];
const range = comptime keys()[start..end];
@@ -173,18 +170,9 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
comptime var len: usize = precomputed.min_len;
inline while (len <= precomputed.max_len) : (len += 1) {
if (str.len == len) {
const end = comptime brk: {
var i = len_indexes[len];
@setEvalBranchQuota(99999);
while (i < kvs.len and kvs[i].key.len == len) : (i += 1) {}
break :brk i;
};
// This benchmarked faster for both small and large lists of strings than using a big switch statement
// But only so long as the keys are a sorted list.
inline for (len_indexes[len]..end) |i| {
inline for (len_indexes[len]..comptime endLen(len)) |i| {
if (strings.eqlComptimeCheckLenWithType(KeyType, str, kvs[i].key, false)) {
return i;
}
@@ -193,6 +181,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
return null;
}
}
return null;
}
@@ -229,31 +218,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
}
pub fn getASCIIICaseInsensitive(input: anytype) ?V {
return getWithEqlLowercase(input, bun.strings.eqlComptimeIgnoreLen);
}
pub fn getWithEqlLowercase(input: anytype, comptime eql: anytype) ?V {
const Input = @TypeOf(input);
const length = if (@hasField(Input, "len")) input.len else input.length();
if (length < precomputed.min_len or length > precomputed.max_len)
return null;
comptime var i: usize = precomputed.min_len;
inline while (i <= precomputed.max_len) : (i += 1) {
if (length == i) {
const lowerbuf: [i]u8 = brk: {
var buf: [i]u8 = undefined;
for (input, &buf) |c, *j| {
j.* = std.ascii.toLower(c);
}
break :brk buf;
};
return getWithLengthAndEql(&lowerbuf, i, eql);
}
}
return null;
return getCaseInsensitiveWithEql(input, bun.strings.eqlComptimeIgnoreLen);
}
pub fn getWithEql(input: anytype, comptime eql: anytype) ?V {
@@ -272,9 +237,7 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
return null;
}
pub fn getAnyCase(input: anytype) ?V {
return getCaseInsensitiveWithEql(input, bun.strings.eqlComptimeIgnoreLen);
}
pub const getAnyCase = getASCIIICaseInsensitive;
pub fn getCaseInsensitiveWithEql(input: anytype, comptime eql: anytype) ?V {
const Input = @TypeOf(input);
@@ -282,20 +245,11 @@ pub fn ComptimeStringMapWithKeyType(comptime KeyType: type, comptime V: type, co
if (length < precomputed.min_len or length > precomputed.max_len)
return null;
comptime var i: usize = precomputed.min_len;
inline while (i <= precomputed.max_len) : (i += 1) {
var lowerbuf: [precomputed.max_len]u8 = undefined;
inline for (precomputed.min_len..precomputed.max_len + 1) |i| {
if (length == i) {
const lowercased: [i]u8 = brk: {
var buf: [i]u8 = undefined;
for (input[0..i], &buf) |c, *b| {
b.* = switch (c) {
'A'...'Z' => c + 32,
else => c,
};
}
break :brk buf;
};
return getWithLengthAndEql(&lowercased, i, eql);
toLowerCased(lowerbuf[0..i], input);
return getWithLengthAndEql(lowerbuf[0..i], i, eql);
}
}
@@ -328,6 +282,15 @@ pub fn ComptimeStringMap16(comptime V: type, comptime kvs_list: anytype) type {
return ComptimeStringMapWithKeyType(u16, V, kvs_list);
}
fn toLowerCased(buf: []u8, input: []const u8) void {
for (input[0..input.len], buf[0..input.len]) |c, *b| {
b.* = switch (c) {
'A'...'Z' => c + 32,
else => c,
};
}
}
const TestEnum = enum {
A,
B,

View File

@@ -126,10 +126,10 @@ pub const DefineData = struct {
const value = if (value_is_undefined or strings.eqlComptime(value_str, "undefined"))
js_ast.Expr.Data{ .e_undefined = js_ast.E.Undefined{} }
else
js_ast.Expr.Data{ .e_identifier = .{
js_ast.Expr.Data{ .e_identifier = js_ast.E.Identifier.init(.{
.ref = Ref.None,
.can_be_removed_if_unused = true,
} };
}) };
return .{
.value = value,

File diff suppressed because it is too large Load Diff

View File

@@ -2178,7 +2178,7 @@ fn NewPrinter(
if (value.loc_ref.ref.?.eql(id.ref)) {
if (p.options.commonjs_named_exports_deoptimized or value.needs_decl) {
if (p.options.commonjs_module_exports_assigned_deoptimized and
id.base == .module_dot_exports and
id.base() == .module_dot_exports and
p.options.commonjs_module_ref.isValid())
{
p.printSymbol(p.options.commonjs_module_ref);
@@ -2926,7 +2926,7 @@ fn NewPrinter(
didPrint = true;
if (p.call_target) |target| {
wrap = e.was_originally_identifier and (target == .e_identifier and
wrap = e.was_originally_identifier() and (target == .e_identifier and
target.e_identifier.ref.eql(expr.data.e_import_identifier.ref));
}
@@ -2956,7 +2956,7 @@ fn NewPrinter(
didPrint = true;
const wrap = if (p.call_target) |target|
e.was_originally_identifier and (target == .e_identifier and
e.was_originally_identifier() and (target == .e_identifier and
target.e_identifier.ref.eql(expr.data.e_import_identifier.ref))
else
false;

View File

@@ -836,7 +836,7 @@ pub const Loader = enum(u8) {
slice = slice[1..];
}
return names.getWithEql(slice, strings.eqlCaseInsensitiveASCIIICheckLength);
return names.getASCIIICaseInsensitive(slice);
}
pub fn supportsClientEntryPoint(this: Loader) bool {