diff options
Diffstat (limited to 'src/js_ast.zig')
-rw-r--r-- | src/js_ast.zig | 1569 |
1 files changed, 1249 insertions, 320 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig index dd5efd65b..f8adaa953 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -13,6 +13,7 @@ const stringZ = bun.stringZ; const default_allocator = bun.default_allocator; const C = bun.C; const Ref = @import("ast/base.zig").Ref; +const Index = @import("ast/base.zig").Index; const RefHashCtx = @import("ast/base.zig").RefHashCtx; const ObjectPool = @import("./pool.zig").ObjectPool; const ImportRecord = @import("import_record.zig").ImportRecord; @@ -23,7 +24,16 @@ const RefCtx = @import("./ast/base.zig").RefCtx; const JSONParser = bun.JSON; const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false; const ComptimeStringMap = bun.ComptimeStringMap; -const JSPrinter = bun.js_printer; +const JSPrinter = @import("./js_printer.zig"); +const ThreadlocalArena = @import("./mimalloc_arena.zig").Arena; + +/// This is the index to the automatically-generated part containing code that +/// calls "__export(exports, { ... getters ... })". This is used to generate +/// getters on an exports object for ES6 export statements, and is both for +/// ES6 star imports and CommonJS-style modules. All files have one of these, +/// although it may contain no statements if there is nothing to export. +pub const namespace_export_part_index = 0; + pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { var max_size = 0; var max_align = 1; @@ -99,21 +109,38 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { pub threadlocal var _self: *Self = undefined; pub fn reclaim() []*Block { - if (_self.overflow.used == 0) return &[_]*Block{}; + var overflow = &_self.overflow; + + if (overflow.used == 0) { + if (overflow.allocated == 0 or overflow.ptrs[0].used == 0) { + return &.{}; + } + } + + var to_move = overflow.ptrs[0..overflow.allocated][overflow.used..]; - var used = _self.overflow.allocator.dupe(*Block, _self.overflow.slice()) catch unreachable; - var new_head = _self.overflow.allocator.create(Block) catch unreachable; - new_head.* = .{}; + // This returns the list of maxed out blocks + var used_list = overflow.slice(); - var to_move = _self.overflow.ptrs[0.._self.overflow.allocated][_self.overflow.used..]; - if (to_move.len > 0) { + // The last block may be partially used. + if (overflow.allocated > overflow.used and to_move.len > 0 and to_move.ptr[0].used > 0) { to_move = to_move[1..]; + used_list.len += 1; } - bun.copy(*Block, _self.overflow.ptrs[1..], to_move); - _self.overflow.ptrs[0] = new_head; - _self.overflow.allocated = 1 + @truncate(Overflow.UsedSize, to_move.len); - reset(); + var used = overflow.allocator.dupe(*Block, used_list) catch unreachable; + + for (to_move, overflow.ptrs[0..to_move.len]) |b, *out| { + b.* = Block{ + .items = undefined, + .used = 0, + }; + out.* = b; + } + + overflow.allocated = @truncate(Overflow.UsedSize, to_move.len); + overflow.used = 0; + return used; } @@ -150,25 +177,27 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { fn deinit() void { var sliced = _self.overflow.slice(); + var allocator = _self.overflow.allocator; if (sliced.len > 1) { var i: usize = 1; const end = sliced.len; while (i < end) { var ptrs = @ptrCast(*[2]Block, sliced[i]); - default_allocator.free(ptrs); + allocator.free(ptrs); i += 2; } _self.overflow.allocated = 1; } var base_store = @fieldParentPtr(WithBase, "store", _self); if (_self.overflow.ptrs[0] == &base_store.head) { - default_allocator.destroy(base_store); + allocator.destroy(base_store); } _self = undefined; } - pub fn append(comptime ValueType: type, value: ValueType) *ValueType { + pub fn append(comptime Disabler: type, comptime ValueType: type, value: ValueType) *ValueType { + Disabler.assert(); return _self._append(ValueType, value); } @@ -219,7 +248,7 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { pub const BindingNodeIndex = Binding; pub const StmtNodeIndex = Stmt; pub const ExprNodeIndex = Expr; -pub const BabyList = @import("./baby_list.zig").BabyList; +pub const BabyList = bun.BabyList; /// Slice that stores capacity and length in the same space as a regular slice. pub const ExprNodeList = BabyList(Expr); @@ -249,7 +278,7 @@ pub const AssignTarget = enum(u2) { } }; -pub const LocRef = struct { loc: logger.Loc, ref: ?Ref = null }; +pub const LocRef = struct { loc: logger.Loc = logger.Loc.Empty, ref: ?Ref = null }; pub const Flags = struct { pub const JSXElement = enum { @@ -337,7 +366,7 @@ pub const Binding = struct { } pub fn toExpr(binding: *const Binding, wrapper: anytype) Expr { - var loc = binding.loc; + const loc = binding.loc; switch (binding.data) { .b_missing => { @@ -370,24 +399,26 @@ pub const Binding = struct { var properties = wrapper .allocator .alloc(G.Property, b.properties.len) catch unreachable; - var i: usize = 0; - while (i < properties.len) : (i += 1) { - const item = b.properties[i]; - properties[i] = G.Property{ + for (properties, b.properties) |*property, item| { + property.* = .{ .flags = item.flags, .key = item.key, .kind = if (item.flags.contains(.is_spread)) - G.Property.Kind.spread + .spread else - G.Property.Kind.normal, + .normal, .value = toExpr(&item.value, wrapper), .initializer = item.default_value, }; } - return Expr.init(E.Object, E.Object{ - .properties = G.Property.List.init(properties), - .is_single_line = b.is_single_line, - }, loc); + return Expr.init( + E.Object, + E.Object{ + .properties = G.Property.List.init(properties), + .is_single_line = b.is_single_line, + }, + loc, + ); }, else => { Global.panic("Interanl error", .{}); @@ -498,8 +529,8 @@ pub const B = union(Binding.Tag) { }; pub const ClauseItem = struct { - alias: string, - alias_loc: logger.Loc, + alias: string = "", + alias_loc: logger.Loc = logger.Loc.Empty, name: LocRef, /// This is the original name of the symbol stored in "Name". It's needed for @@ -510,7 +541,7 @@ pub const ClauseItem = struct { /// In this case both "foo" and "bar" are aliases because it's a re-export. /// We need to preserve both aliases in case the symbol is renamed. In this /// example, "foo" is "OriginalName" and "bar" is "Alias". - original_name: string, + original_name: string = "", pub const default_alias: string = "default"; }; @@ -623,131 +654,191 @@ pub const G = struct { }; pub const Symbol = struct { - // This is the name that came from the parser. Printed names may be renamed - // during minification or to avoid name collisions. Do not use the original - // name during printing. + /// This is the name that came from the parser. Printed names may be renamed + /// during minification or to avoid name collisions. Do not use the original + /// name during printing. original_name: string, - // This is used for symbols that represent items in the import clause of an - // ES6 import statement. These should always be referenced by EImportIdentifier - // instead of an EIdentifier. When this is present, the expression should - // be printed as a property access off the namespace instead of as a bare - // identifier. - // - // For correctness, this must be stored on the symbol instead of indirectly - // associated with the Ref for the symbol somehow. In ES6 "flat bundling" - // mode, re-exported symbols are collapsed using MergeSymbols() and renamed - // symbols from other files that end up at this symbol must be able to tell - // if it has a namespace alias. + /// This is used for symbols that represent items in the import clause of an + /// ES6 import statement. These should always be referenced by EImportIdentifier + /// instead of an EIdentifier. When this is present, the expression should + /// be printed as a property access off the namespace instead of as a bare + /// identifier. + /// + /// For correctness, this must be stored on the symbol instead of indirectly + /// associated with the Ref for the symbol somehow. In ES6 "flat bundling" + /// mode, re-exported symbols are collapsed using MergeSymbols() and renamed + /// symbols from other files that end up at this symbol must be able to tell + /// if it has a namespace alias. namespace_alias: ?G.NamespaceAlias = null, - // Used by the parser for single pass parsing. + /// Used by the parser for single pass parsing. link: Ref = Ref.None, - // An estimate of the number of uses of this symbol. This is used to detect - // whether a symbol is used or not. For example, TypeScript imports that are - // unused must be removed because they are probably type-only imports. This - // is an estimate and may not be completely accurate due to oversights in the - // code. But it should always be non-zero when the symbol is used. + /// An estimate of the number of uses of this symbol. This is used to detect + /// whether a symbol is used or not. For example, TypeScript imports that are + /// unused must be removed because they are probably type-only imports. This + /// is an estimate and may not be completely accurate due to oversights in the + /// code. But it should always be non-zero when the symbol is used. use_count_estimate: u32 = 0, - // This is for generating cross-chunk imports and exports for code splitting. - chunk_index: ?u32 = null, - - // This is used for minification. Symbols that are declared in sibling scopes - // can share a name. A good heuristic (from Google Closure Compiler) is to - // assign names to symbols from sibling scopes in declaration order. That way - // local variable names are reused in each global function like this, which - // improves gzip compression: - // - // function x(a, b) { ... } - // function y(a, b, c) { ... } - // - // The parser fills this in for symbols inside nested scopes. There are three - // slot namespaces: regular symbols, label symbols, and private symbols. - nested_scope_slot: ?u32 = null, + /// This is for generating cross-chunk imports and exports for code splitting. + /// + /// Do not use this directly. Use `chunkIndex()` instead. + chunk_index: u32 = invalid_chunk_index, + + /// This is used for minification. Symbols that are declared in sibling scopes + /// can share a name. A good heuristic (from Google Closure Compiler) is to + /// assign names to symbols from sibling scopes in declaration order. That way + /// local variable names are reused in each global function like this, which + /// improves gzip compression: + /// + /// function x(a, b) { ... } + /// function y(a, b, c) { ... } + /// + /// The parser fills this in for symbols inside nested scopes. There are three + /// slot namespaces: regular symbols, label symbols, and private symbols. + /// + /// Do not use this directly. Use `nestedScopeSlot()` instead. + nested_scope_slot: u32 = invalid_nested_scope_slot, + /// The kind of symbol. This is used to determine how to print the symbol + /// and how to deal with conflicts, renaming, etc. kind: Kind = Kind.other, - // Certain symbols must not be renamed or minified. For example, the - // "arguments" variable is declared by the runtime for every function. - // Renaming can also break any identifier used inside a "with" statement. + /// Certain symbols must not be renamed or minified. For example, the + /// "arguments" variable is declared by the runtime for every function. + /// Renaming can also break any identifier used inside a "with" statement. must_not_be_renamed: bool = false, - // We automatically generate import items for property accesses off of - // namespace imports. This lets us remove the expensive namespace imports - // while bundling in many cases, replacing them with a cheap import item - // instead: - // - // import * as ns from 'path' - // ns.foo() - // - // That can often be replaced by this, which avoids needing the namespace: - // - // import {foo} from 'path' - // foo() - // - // However, if the import is actually missing then we don't want to report a - // compile-time error like we do for real import items. This status lets us - // avoid this. We also need to be able to replace such import items with - // undefined, which this status is also used for. + /// We automatically generate import items for property accesses off of + /// namespace imports. This lets us remove the expensive namespace imports + /// while bundling in many cases, replacing them with a cheap import item + /// instead: + /// + /// import * as ns from 'path' + /// ns.foo() + /// + /// That can often be replaced by this, which avoids needing the namespace: + /// + /// import {foo} from 'path' + /// foo() + /// + /// However, if the import is actually missing then we don't want to report a + /// compile-time error like we do for real import items. This status lets us + /// avoid this. We also need to be able to replace such import items with + /// undefined, which this status is also used for. import_item_status: ImportItemStatus = ImportItemStatus.none, - // Sometimes we lower private symbols even if they are supported. For example, - // consider the following TypeScript code: - // - // class Foo { - // #foo = 123 - // bar = this.#foo - // } - // - // If "useDefineForClassFields: false" is set in "tsconfig.json", then "bar" - // must use assignment semantics instead of define semantics. We can compile - // that to this code: - // - // class Foo { - // constructor() { - // this.#foo = 123; - // this.bar = this.#foo; - // } - // #foo; - // } - // - // However, we can't do the same for static fields: - // - // class Foo { - // static #foo = 123 - // static bar = this.#foo - // } - // - // Compiling these static fields to something like this would be invalid: - // - // class Foo { - // static #foo; - // } - // Foo.#foo = 123; - // Foo.bar = Foo.#foo; - // - // Thus "#foo" must be lowered even though it's supported. Another case is - // when we're converting top-level class declarations to class expressions - // to avoid the TDZ and the class shadowing symbol is referenced within the - // class body: - // - // class Foo { - // static #foo = Foo - // } - // - // This cannot be converted into something like this: - // - // var Foo = class { - // static #foo; - // }; - // Foo.#foo = Foo; - // + /// --- Not actually used yet ----------------------------------------------- + /// Sometimes we lower private symbols even if they are supported. For example, + /// consider the following TypeScript code: + /// + /// class Foo { + /// #foo = 123 + /// bar = this.#foo + /// } + /// + /// If "useDefineForClassFields: false" is set in "tsconfig.json", then "bar" + /// must use assignment semantics instead of define semantics. We can compile + /// that to this code: + /// + /// class Foo { + /// constructor() { + /// this.#foo = 123; + /// this.bar = this.#foo; + /// } + /// #foo; + /// } + /// + /// However, we can't do the same for static fields: + /// + /// class Foo { + /// static #foo = 123 + /// static bar = this.#foo + /// } + /// + /// Compiling these static fields to something like this would be invalid: + /// + /// class Foo { + /// static #foo; + /// } + /// Foo.#foo = 123; + /// Foo.bar = Foo.#foo; + /// + /// Thus "#foo" must be lowered even though it's supported. Another case is + /// when we're converting top-level class declarations to class expressions + /// to avoid the TDZ and the class shadowing symbol is referenced within the + /// class body: + /// + /// class Foo { + /// static #foo = Foo + /// } + /// + /// This cannot be converted into something like this: + /// + /// var Foo = class { + /// static #foo; + /// }; + /// Foo.#foo = Foo; + /// + /// --- Not actually used yet ----------------------------------------------- private_symbol_must_be_lowered: bool = false, + remove_overwritten_function_declaration: bool = false, + + /// In debug mode, sometimes its helpful to know what source file + /// A symbol came from. This is used for that. + /// + /// We don't want this in non-debug mode because it increases the size of + /// the symbol table. + debug_mode_source_index: if (Environment.allow_assert) + Index.Int + else + u0 = 0, + + const invalid_chunk_index = std.math.maxInt(u32); + const invalid_nested_scope_slot = std.math.maxInt(u32); + + pub const SlotNamespace = enum { + default, + label, + private_name, + mangled_prop, + must_not_be_renamed, + }; + + /// This is for generating cross-chunk imports and exports for code splitting. + pub inline fn chunkIndex(this: *const Symbol) ?u32 { + const i = this.chunk_index; + return if (i == invalid_chunk_index) null else i; + } + + pub inline fn nestedScopeSlot(this: *const Symbol) ?u32 { + const i = this.nested_scope_slot; + return if (i == invalid_nested_scope_slot) null else i; + } + + pub fn slotNamespace(this: *const Symbol) SlotNamespace { + const kind = this.kind; + + if (kind == .unbound or this.must_not_be_renamed) { + return .must_not_be_renamed; + } + + if (kind.isPrivate()) { + return .private_name; + } + + return switch (kind) { + // .mangled_prop => .mangled_prop, + .label => .label, + else => .default, + }; + } + pub inline fn hasLink(this: *const Symbol) bool { - return !this.link.isNull(); + return this.link.tag != .invalid; } pub const Kind = enum { @@ -835,12 +926,55 @@ pub const Symbol = struct { pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void { return try std.json.stringify(@tagName(self), opts, o); } + + pub inline fn isPrivate(kind: Symbol.Kind) bool { + return @enumToInt(kind) >= @enumToInt(Symbol.Kind.private_field) and @enumToInt(kind) <= @enumToInt(Symbol.Kind.private_static_get_set_pair); + } + + pub inline fn isHoisted(kind: Symbol.Kind) bool { + return switch (kind) { + .hoisted, .hoisted_function => true, + else => false, + }; + } + + pub inline fn isHoistedOrFunction(kind: Symbol.Kind) bool { + return switch (kind) { + .hoisted, .hoisted_function, .generator_or_async_function => true, + else => false, + }; + } + + pub inline fn isFunction(kind: Symbol.Kind) bool { + return switch (kind) { + .hoisted_function, .generator_or_async_function => true, + else => false, + }; + } }; + pub const isKindPrivate = Symbol.Kind.isPrivate; + pub const isKindHoisted = Symbol.Kind.isHoisted; + pub const isKindHoistedOrFunction = Symbol.Kind.isHoistedOrFunction; + pub const isKindFunction = Symbol.Kind.isFunction; + pub const Use = struct { count_estimate: u32 = 0, }; + pub const List = BabyList(Symbol); + pub const NestedList = BabyList(List); + + pub fn mergeContentsWith(this: *Symbol, old: *Symbol) void { + this.use_count_estimate += old.use_count_estimate; + if (old.must_not_be_renamed) { + this.original_name = old.original_name; + this.must_not_be_renamed = true; + } + + // TODO: MustStartWithCapitalLetterForJSX + } + pub const Map = struct { // This could be represented as a "map[Ref]Symbol" but a two-level array was // more efficient in profiles. This appears to be because it doesn't involve @@ -849,34 +983,94 @@ pub const Symbol = struct { // single inner array, so you can join the maps together by just make a // single outer array containing all of the inner arrays. See the comment on // "Ref" for more detail. - symbols_for_source: [][]Symbol, + symbols_for_source: NestedList = NestedList{}, - pub fn get(self: *Map, ref: Ref) ?*Symbol { + pub fn dump(this: Map) void { + defer Output.flush(); + for (this.symbols_for_source.slice(), 0..) |symbols, i| { + Output.prettyln("\n\n-- Source ID: {d} ({d} symbols) --\n\n", .{ i, symbols.len }); + for (symbols.slice(), 0..) |symbol, inner_index| { + Output.prettyln( + " name: {s}\n tag: {s}\n {any}\n", + .{ + symbol.original_name, @tagName(symbol.kind), + if (symbol.hasLink()) symbol.link else Ref{ + .source_index = @truncate(Ref.Int, i), + .inner_index = @truncate(Ref.Int, inner_index), + .tag = .symbol, + }, + }, + ); + } + } + } + + pub fn assignChunkIndex(this: *Map, decls_: DeclaredSymbol.List, chunk_index: u32) void { + const Iterator = struct { + map: *Map, + chunk_index: u32, + + pub fn next(self: @This(), ref: Ref) void { + var symbol = self.map.get(ref).?; + symbol.chunk_index = self.chunk_index; + } + }; + var decls = decls_; + + DeclaredSymbol.forEachTopLevelSymbol(&decls, Iterator{ .map = this, .chunk_index = chunk_index }, Iterator.next); + } + + pub fn merge(this: *Map, old: Ref, new: Ref) Ref { + if (old.eql(new)) { + return new; + } + + var old_symbol = this.get(old).?; + if (old_symbol.hasLink()) { + const old_link = old_symbol.link; + old_symbol.link = this.merge(old_link, new); + return old_symbol.link; + } + + var new_symbol = this.get(new).?; + + if (new_symbol.hasLink()) { + const new_link = new_symbol.link; + new_symbol.link = this.merge(old, new_link); + return new_symbol.link; + } + + old_symbol.link = new; + new_symbol.mergeContentsWith(old_symbol); + return new; + } + + pub fn get(self: *const Map, ref: Ref) ?*Symbol { if (Ref.isSourceIndexNull(ref.sourceIndex()) or ref.isSourceContentsSlice()) { return null; } - return &self.symbols_for_source[ref.sourceIndex()][ref.innerIndex()]; + return self.symbols_for_source.at(ref.sourceIndex()).mut(ref.innerIndex()); } - pub fn getConst(self: *Map, ref: Ref) ?*const Symbol { + pub fn getConst(self: *const Map, ref: Ref) ?*const Symbol { if (Ref.isSourceIndexNull(ref.sourceIndex()) or ref.isSourceContentsSlice()) { return null; } - return &self.symbols_for_source[ref.sourceIndex()][ref.innerIndex()]; + return self.symbols_for_source.at(ref.sourceIndex()).at(ref.innerIndex()); } pub fn init(sourceCount: usize, allocator: std.mem.Allocator) !Map { - var symbols_for_source: [][]Symbol = try allocator.alloc([]Symbol, sourceCount); + var symbols_for_source: NestedList = NestedList.init(try allocator.alloc([]Symbol, sourceCount)); return Map{ .symbols_for_source = symbols_for_source }; } - pub fn initList(list: [][]Symbol) Map { + pub fn initList(list: NestedList) Map { return Map{ .symbols_for_source = list }; } - pub fn getWithLink(symbols: *Map, ref: Ref) ?*Symbol { + pub fn getWithLink(symbols: *const Map, ref: Ref) ?*Symbol { var symbol: *Symbol = symbols.get(ref) orelse return null; if (symbol.hasLink()) { return symbols.get(symbol.link) orelse symbol; @@ -892,52 +1086,35 @@ pub const Symbol = struct { return symbol; } - pub fn follow(symbols: *Map, ref: Ref) Ref { - if (symbols.get(ref)) |symbol| { - const link = symbol.link; - if (link.isNull()) - return ref; - - if (link.eql(ref)) { - symbol.link = ref; + pub fn followAll(symbols: *Map) void { + for (symbols.symbols_for_source.slice()) |list| { + for (list.slice()) |*symbol| { + if (!symbol.hasLink()) continue; + symbol.link = follow(symbols, symbol.link); } + } + } - return symbol.link; - } else { + pub fn follow(symbols: *const Map, ref: Ref) Ref { + var symbol = symbols.get(ref) orelse return ref; + if (!symbol.hasLink()) { return ref; } - } - }; - pub inline fn isKindPrivate(kind: Symbol.Kind) bool { - return @enumToInt(kind) >= @enumToInt(Symbol.Kind.private_field) and @enumToInt(kind) <= @enumToInt(Symbol.Kind.private_static_get_set_pair); - } + const link = follow(symbols, symbol.link); - pub inline fn isKindHoisted(kind: Symbol.Kind) bool { - return switch (kind) { - .hoisted, .hoisted_function => true, - else => false, - }; - } + if (!symbol.link.eql(link)) { + symbol.link = link; + } + + return link; + } + }; pub inline fn isHoisted(self: *const Symbol) bool { return Symbol.isKindHoisted(self.kind); } - pub inline fn isKindHoistedOrFunction(kind: Symbol.Kind) bool { - return switch (kind) { - .hoisted, .hoisted_function, .generator_or_async_function => true, - else => false, - }; - } - - pub inline fn isKindFunction(kind: Symbol.Kind) bool { - return switch (kind) { - .hoisted_function, .generator_or_async_function => true, - else => false, - }; - } - pub fn isReactComponentishName(symbol: *const Symbol) bool { switch (symbol.kind) { .hoisted, .hoisted_function, .cconst, .class, .other => { @@ -1095,13 +1272,13 @@ pub const E = struct { target: ExprNodeIndex, optional_chain: ?OptionalChain = null, - pub fn hasSameFlagsAs(a: *Index, b: *Index) bool { + pub fn hasSameFlagsAs(a: *E.Index, b: *E.Index) bool { return (a.optional_chain == b.optional_chain); } }; pub const Arrow = struct { - args: []G.Arg, + args: []G.Arg = &[_]G.Arg{}, body: G.FnBody, is_async: bool = false, @@ -1140,34 +1317,38 @@ pub const E = struct { } }; - // This is similar to an EIdentifier but it represents a reference to an ES6 - // import item. - // - // Depending on how the code is linked, the file containing this EImportIdentifier - // may or may not be in the same module group as the file it was imported from. - // - // If it's the same module group than we can just merge the import item symbol - // with the corresponding symbol that was imported, effectively renaming them - // to be the same thing and statically binding them together. - // - // But if it's a different module group, then the import must be dynamically - // evaluated using a property access off the corresponding namespace symbol, - // which represents the result of a require() call. - // - // It's stored as a separate type so it's not easy to confuse with a plain - // identifier. For example, it'd be bad if code trying to convert "{x: x}" into - // "{x}" shorthand syntax wasn't aware that the "x" in this case is actually - // "{x: importedNamespace.x}". This separate type forces code to opt-in to - // doing this instead of opt-out. + /// This is similar to an `Identifier` but it represents a reference to an ES6 + /// import item. + /// + /// Depending on how the code is linked, the file containing this EImportIdentifier + /// may or may not be in the same module group as the file it was imported from. + /// + /// If it's the same module group than we can just merge the import item symbol + /// with the corresponding symbol that was imported, effectively renaming them + /// to be the same thing and statically binding them together. + /// + /// But if it's a different module group, then the import must be dynamically + /// evaluated using a property access off the corresponding namespace symbol, + /// which represents the result of a require() call. + /// + /// It's stored as a separate type so it's not easy to confuse with a plain + /// identifier. For example, it'd be bad if code trying to convert "{x: x}" into + /// "{x}" shorthand syntax wasn't aware that the "x" in this case is actually + /// "{x: importedNamespace.x}". This separate type forces code to opt-in to + /// doing this instead of opt-out. pub const ImportIdentifier = struct { ref: Ref = Ref.None, - // 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. + /// 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 const CommonJSExportIdentifier = struct { + ref: Ref = Ref.None, + }; + // This is similar to EIdentifier but it represents class-private fields and // methods. It can be used where computed properties can be used, such as // EIndex and Property. @@ -1914,15 +2095,19 @@ pub const E = struct { expr: ExprNodeIndex, import_record_index: u32, - // Comments inside "import()" expressions have special meaning for Webpack. - // Preserving comments inside these expressions makes it possible to use - // esbuild as a TypeScript-to-JavaScript frontend for Webpack to improve - // performance. We intentionally do not interpret these comments in esbuild - // because esbuild is not Webpack. But we do preserve them since doing so is - // harmless, easy to maintain, and useful to people. See the Webpack docs for - // more info: https://webpack.js.org/api/module-methods/#magic-comments. - // TODO: + /// Comments inside "import()" expressions have special meaning for Webpack. + /// Preserving comments inside these expressions makes it possible to use + /// esbuild as a TypeScript-to-JavaScript frontend for Webpack to improve + /// performance. We intentionally do not interpret these comments in esbuild + /// because esbuild is not Webpack. But we do preserve them since doing so is + /// harmless, easy to maintain, and useful to people. See the Webpack docs for + /// more info: https://webpack.js.org/api/module-methods/#magic-comments. + /// TODO: leading_interior_comments: []G.Comment = &([_]G.Comment{}), + + pub fn isImportRecordNull(this: *const Import) bool { + return this.import_record_index == std.math.maxInt(u32); + } }; }; @@ -1930,6 +2115,18 @@ pub const Stmt = struct { loc: logger.Loc, data: Data, + pub const Batcher = bun.Batcher(Stmt); + + pub fn assign(a: Expr, b: Expr, allocator: std.mem.Allocator) Stmt { + return Stmt.alloc( + S.SExpr, + S.SExpr{ + .value = Expr.assign(a, b, allocator), + }, + a.loc, + ); + } + const Serializable = struct { type: Tag, object: string, @@ -1963,6 +2160,7 @@ pub const Stmt = struct { pub var icount: usize = 0; pub fn init(comptime StatementType: type, origData: *StatementType, loc: logger.Loc) Stmt { icount += 1; + return switch (comptime StatementType) { S.Empty => Stmt{ .loc = loc, .data = Data{ .s_empty = S.Empty{} } }, S.Block => Stmt.comptime_init("s_block", S.Block, origData, loc), @@ -2002,7 +2200,24 @@ pub const Stmt = struct { }; } inline fn comptime_alloc(comptime tag_name: string, comptime typename: type, origData: anytype, loc: logger.Loc) Stmt { - return Stmt{ .loc = loc, .data = @unionInit(Data, tag_name, Data.Store.append(typename, origData)) }; + return Stmt{ + .loc = loc, + .data = @unionInit( + Data, + tag_name, + Data.Store.append( + typename, + origData, + ), + ), + }; + } + + fn allocateData(allocator: std.mem.Allocator, comptime tag_name: string, comptime typename: type, origData: anytype, loc: logger.Loc) Stmt { + var value = allocator.create(@TypeOf(origData)) catch unreachable; + value.* = origData; + + return comptime_init(tag_name, *typename, value, loc); } inline fn comptime_init(comptime tag_name: string, comptime TypeName: type, origData: TypeName, loc: logger.Loc) Stmt { @@ -2010,6 +2225,8 @@ pub const Stmt = struct { } pub fn alloc(comptime StatementData: type, origData: StatementData, loc: logger.Loc) Stmt { + Stmt.Data.Store.assert(); + icount += 1; return switch (StatementData) { S.Block => Stmt.comptime_alloc("s_block", S.Block, origData, loc), @@ -2049,6 +2266,53 @@ pub const Stmt = struct { }; } + pub const Disabler = bun.DebugOnlyDisabler(@This()); + + /// When the lifetime of an Stmt.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 Stmt.init or Stmt.alloc when possible. This will be slower. + pub fn allocate(allocator: std.mem.Allocator, comptime StatementData: type, origData: StatementData, loc: logger.Loc) Stmt { + Stmt.Data.Store.assert(); + + icount += 1; + return switch (StatementData) { + S.Block => Stmt.allocateData(allocator, "s_block", S.Block, origData, loc), + S.Break => Stmt.allocateData(allocator, "s_break", S.Break, origData, loc), + S.Class => Stmt.allocateData(allocator, "s_class", S.Class, origData, loc), + S.Comment => Stmt.allocateData(allocator, "s_comment", S.Comment, origData, loc), + S.Continue => Stmt.allocateData(allocator, "s_continue", S.Continue, origData, loc), + S.Debugger => Stmt{ .loc = loc, .data = .{ .s_debugger = origData } }, + S.Directive => Stmt.allocateData(allocator, "s_directive", S.Directive, origData, loc), + S.DoWhile => Stmt.allocateData(allocator, "s_do_while", S.DoWhile, origData, loc), + S.Empty => Stmt{ .loc = loc, .data = Data{ .s_empty = S.Empty{} } }, + S.Enum => Stmt.allocateData(allocator, "s_enum", S.Enum, origData, loc), + S.ExportClause => Stmt.allocateData(allocator, "s_export_clause", S.ExportClause, origData, loc), + S.ExportDefault => Stmt.allocateData(allocator, "s_export_default", S.ExportDefault, origData, loc), + S.ExportEquals => Stmt.allocateData(allocator, "s_export_equals", S.ExportEquals, origData, loc), + S.ExportFrom => Stmt.allocateData(allocator, "s_export_from", S.ExportFrom, origData, loc), + S.ExportStar => Stmt.allocateData(allocator, "s_export_star", S.ExportStar, origData, loc), + S.SExpr => Stmt.allocateData(allocator, "s_expr", S.SExpr, origData, loc), + S.ForIn => Stmt.allocateData(allocator, "s_for_in", S.ForIn, origData, loc), + S.ForOf => Stmt.allocateData(allocator, "s_for_of", S.ForOf, origData, loc), + S.For => Stmt.allocateData(allocator, "s_for", S.For, origData, loc), + S.Function => Stmt.allocateData(allocator, "s_function", S.Function, origData, loc), + S.If => Stmt.allocateData(allocator, "s_if", S.If, origData, loc), + S.Import => Stmt.allocateData(allocator, "s_import", S.Import, origData, loc), + S.Label => Stmt.allocateData(allocator, "s_label", S.Label, origData, loc), + S.LazyExport => Stmt.allocateData(allocator, "s_lazy_export", S.LazyExport, origData, loc), + S.Local => Stmt.allocateData(allocator, "s_local", S.Local, origData, loc), + S.Namespace => Stmt.allocateData(allocator, "s_namespace", S.Namespace, origData, loc), + S.Return => Stmt.allocateData(allocator, "s_return", S.Return, origData, loc), + S.Switch => Stmt.allocateData(allocator, "s_switch", S.Switch, origData, loc), + S.Throw => Stmt.allocateData(allocator, "s_throw", S.Throw, origData, loc), + S.Try => Stmt.allocateData(allocator, "s_try", S.Try, origData, loc), + S.TypeScript => Stmt{ .loc = loc, .data = Data{ .s_type_script = S.TypeScript{} } }, + S.While => Stmt.allocateData(allocator, "s_while", S.While, origData, loc), + S.With => Stmt.allocateData(allocator, "s_with", S.With, origData, loc), + else => @compileError("Invalid type in Stmt.init"), + }; + } + pub const Tag = enum(u6) { s_block, s_break, @@ -2166,12 +2430,13 @@ pub const Stmt = struct { S.While, S.With, }; - pub const All = NewBaseStore(Union, 128); + const All = NewBaseStore(Union, 128); + pub threadlocal var memory_allocator: ?*ASTMemoryAllocator = null; threadlocal var has_inited = false; pub threadlocal var disable_reset = false; pub fn create(allocator: std.mem.Allocator) void { - if (has_inited) { + if (has_inited or memory_allocator != null) { return; } @@ -2180,25 +2445,29 @@ pub const Stmt = struct { } pub fn reset() void { - if (disable_reset) return; + if (disable_reset or memory_allocator != null) return; All.reset(); } pub fn deinit() void { - if (!has_inited) return; + if (!has_inited or memory_allocator != null) return; All.deinit(); has_inited = false; } - pub fn assert() void { + pub inline fn assert() void { if (comptime Environment.allow_assert) { - if (!has_inited) + if (!has_inited and memory_allocator == null) bun.unreachablePanic("Store must be init'd", .{}); } } pub fn append(comptime ValueType: type, value: anytype) *ValueType { - return All.append(ValueType, value); + if (memory_allocator) |allocator| { + return allocator.append(ValueType, value); + } + + return All.append(Disabler, ValueType, value); } pub fn toOwnedSlice() []*Store.All.Block { @@ -2228,6 +2497,15 @@ pub const Expr = struct { loc: logger.Loc, data: Data, + 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, @@ -2563,39 +2841,449 @@ pub const Expr = struct { return [2]f64{ left.e_number.value, right.e_number.value }; } - pub fn isAnonymousNamed(e: *Expr) bool { - switch (e.data) { - .e_arrow => { - return true; + 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: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; }, - .e_function => |func| { - return func.func.name == null; + E.Class => { + return Expr{ + .loc = loc, + .data = Data{ + .e_class = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; }, - .e_class => |class| { - return class.class_name == null; + E.Unary => { + return Expr{ + .loc = loc, + .data = Data{ + .e_unary = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; }, + E.Binary => { + return Expr{ + .loc = loc, + .data = Data{ + .e_binary = brk: { + var 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: { + var 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: { + var 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: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Dot => { + return Expr{ + .loc = loc, + .data = Data{ + .e_dot = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Index => { + return Expr{ + .loc = loc, + .data = Data{ + .e_index = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Arrow => { + return Expr{ + .loc = loc, + .data = Data{ + .e_arrow = brk: { + var 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: { + var 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: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Object => { + return Expr{ + .loc = loc, + .data = Data{ + .e_object = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Spread => { + return Expr{ + .loc = loc, + .data = Data{ + .e_spread = brk: { + var 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()) { + std.debug.assert(@ptrToInt(st.data.ptr) > 0); + std.debug.assert(st.data[0] > 0); + } + } + return Expr{ + .loc = loc, + .data = Data{ + .e_string = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.TemplatePart => { + return Expr{ + .loc = loc, + .data = Data{ + .e_template_part = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Template => { + return Expr{ + .loc = loc, + .data = Data{ + .e_template = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.RegExp => { + return Expr{ + .loc = loc, + .data = Data{ + .e_reg_exp = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Await => { + return Expr{ + .loc = loc, + .data = Data{ + .e_await = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Yield => { + return Expr{ + .loc = loc, + .data = Data{ + .e_yield = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.If => { + return Expr{ + .loc = loc, + .data = Data{ + .e_if = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.RequireOrRequireResolve => { + return Expr{ + .loc = loc, + .data = Data{ + .e_require_or_require_resolve = st, + }, + }; + }, + E.Import => { + return Expr{ + .loc = loc, + .data = Data{ + .e_import = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st; + break :brk item; + }, + }, + }; + }, + E.Require => { + return Expr{ + .loc = loc, + .data = Data{ + .e_require = st, + }, + }; + }, + *E.String => { + return Expr{ + .loc = loc, + .data = Data{ + .e_string = brk: { + var item = allocator.create(Type) catch unreachable; + item.* = st.*; + break :brk item; + }, + }, + }; + }, + else => { - return false; + @compileError("Invalid type passed to Expr.init: " ++ @typeName(Type)); }, } } - 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 }; + 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.Array => { return Expr{ .loc = loc, .data = Data{ - .e_array = Data.Store.All.append(Type, st), + .e_array = Data.Store.append(Type, st), }, }; }, @@ -2603,7 +3291,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_class = Data.Store.All.append(Type, st), + .e_class = Data.Store.append(Type, st), }, }; }, @@ -2611,7 +3299,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_unary = Data.Store.All.append(Type, st), + .e_unary = Data.Store.append(Type, st), }, }; }, @@ -2619,7 +3307,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_binary = Data.Store.All.append(Type, st), + .e_binary = Data.Store.append(Type, st), }, }; }, @@ -2667,7 +3355,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_new = Data.Store.All.append(Type, st), + .e_new = Data.Store.append(Type, st), }, }; }, @@ -2683,7 +3371,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_function = Data.Store.All.append(Type, st), + .e_function = Data.Store.append(Type, st), }, }; }, @@ -2699,7 +3387,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_call = Data.Store.All.append(Type, st), + .e_call = Data.Store.append(Type, st), }, }; }, @@ -2707,7 +3395,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_dot = Data.Store.All.append(Type, st), + .e_dot = Data.Store.append(Type, st), }, }; }, @@ -2715,7 +3403,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_index = Data.Store.All.append(Type, st), + .e_index = Data.Store.append(Type, st), }, }; }, @@ -2723,7 +3411,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_arrow = Data.Store.All.append(Type, st), + .e_arrow = Data.Store.append(Type, st), }, }; }, @@ -2751,6 +3439,16 @@ pub const Expr = struct { }, }; }, + E.CommonJSExportIdentifier => { + return Expr{ + .loc = loc, + .data = Data{ + .e_commonjs_export_identifier = .{ + .ref = st.ref, + }, + }, + }; + }, E.PrivateIdentifier => { return Expr{ .loc = loc, @@ -2763,7 +3461,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_jsx_element = Data.Store.All.append(Type, st), + .e_jsx_element = Data.Store.append(Type, st), }, }; }, @@ -2782,7 +3480,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_big_int = Data.Store.All.append(Type, st), + .e_big_int = Data.Store.append(Type, st), }, }; }, @@ -2790,7 +3488,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_object = Data.Store.All.append(Type, st), + .e_object = Data.Store.append(Type, st), }, }; }, @@ -2798,7 +3496,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_spread = Data.Store.All.append(Type, st), + .e_spread = Data.Store.append(Type, st), }, }; }, @@ -2813,7 +3511,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_string = Data.Store.All.append(Type, st), + .e_string = Data.Store.append(Type, st), }, }; }, @@ -2821,7 +3519,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_template_part = Data.Store.All.append(Type, st), + .e_template_part = Data.Store.append(Type, st), }, }; }, @@ -2829,7 +3527,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_template = Data.Store.All.append(Type, st), + .e_template = Data.Store.append(Type, st), }, }; }, @@ -2837,7 +3535,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_reg_exp = Data.Store.All.append(Type, st), + .e_reg_exp = Data.Store.append(Type, st), }, }; }, @@ -2845,7 +3543,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_await = Data.Store.All.append(Type, st), + .e_await = Data.Store.append(Type, st), }, }; }, @@ -2853,7 +3551,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_yield = Data.Store.All.append(Type, st), + .e_yield = Data.Store.append(Type, st), }, }; }, @@ -2861,7 +3559,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_if = Data.Store.All.append(Type, st), + .e_if = Data.Store.append(Type, st), }, }; }, @@ -2877,7 +3575,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_import = Data.Store.All.append(Type, st), + .e_import = Data.Store.append(Type, st), }, }; }, @@ -2893,7 +3591,7 @@ pub const Expr = struct { return Expr{ .loc = loc, .data = Data{ - .e_string = Data.Store.All.append(@TypeOf(st.*), st.*), + .e_string = Data.Store.append(@TypeOf(st.*), st.*), }, }; }, @@ -2942,6 +3640,8 @@ pub const Expr = struct { e_class, e_require, + e_commonjs_export_identifier, + // This should never make it to the printer inline_identifier, @@ -3504,16 +4204,6 @@ pub const Expr = struct { return null; } - pub fn assignStmt(a: Expr, b: Expr, allocator: std.mem.Allocator) Stmt { - return Stmt.alloc( - S.SExpr, - S.SExpr{ - .value = Expr.assign(a, b, allocator), - }, - a.loc, - ); - } - pub fn isOptionalChain(self: *const @This()) bool { return switch (self.data) { .e_dot => self.data.e_dot.optional_chain != null, @@ -3594,6 +4284,7 @@ pub const Expr = struct { 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, @@ -3955,7 +4646,7 @@ pub const Expr = struct { const medium = 256; const rare = 24; - pub const All = NewBaseStore( + const All = NewBaseStore( &([_]type{ E.Array, E.Unary, @@ -3986,10 +4677,12 @@ pub const Expr = struct { 512, ); + pub threadlocal var memory_allocator: ?*ASTMemoryAllocator = null; + threadlocal var has_inited = false; pub threadlocal var disable_reset = false; pub fn create(allocator: std.mem.Allocator) void { - if (has_inited) { + if (has_inited or memory_allocator != null) { return; } @@ -3997,34 +4690,34 @@ pub const Expr = struct { _ = All.init(allocator); } - pub fn assert() void { - if (comptime Environment.allow_assert) { - if (!has_inited) - bun.unreachablePanic("Store must be init'd", .{}); - } - } - pub fn reset() void { - if (disable_reset) return; + if (disable_reset or memory_allocator != null) return; All.reset(); } pub fn deinit() void { - if (!has_inited) return; + if (!has_inited or memory_allocator != null) return; All.deinit(); has_inited = false; } + pub inline fn assert() void { + if (comptime Environment.allow_assert) { + if (!has_inited and memory_allocator == null) + bun.unreachablePanic("Store must be init'd", .{}); + } + } + pub fn append(comptime ValueType: type, value: anytype) *ValueType { - if (ValueType == E.Identifier) { - return E.Identifier.append(ValueType, value); - } else { - return All.append(ValueType, value); + if (memory_allocator) |allocator| { + return allocator.append(ValueType, value); } + + return All.append(Disabler, ValueType, value); } pub fn toOwnedSlice() []*Store.All.Block { - if (!has_inited or Store.All._self.overflow.used == 0 or disable_reset) return &[_]*Store.All.Block{}; + if (!has_inited or Store.All._self.overflow.used == 0 or disable_reset or memory_allocator != null) return &[_]*Store.All.Block{}; return Store.All.reclaim(); } }; @@ -4534,9 +5227,11 @@ pub const ArrayBinding = struct { }; pub const Ast = struct { + pub const TopLevelSymbolToParts = std.ArrayHashMapUnmanaged(Ref, BabyList(u32), Ref.ArrayHashCtx, false); + approximate_newline_count: usize = 0, has_lazy_export: bool = false, - runtime_imports: Runtime.Imports, + runtime_imports: Runtime.Imports = .{}, runtime_import_record_id: ?u32 = null, needs_runtime: bool = false, @@ -4554,22 +5249,23 @@ pub const Ast = struct { // This is a list of ES6 features. They are ranges instead of booleans so // that they can be used in log messages. Check to see if "Len > 0". - import_keyword: ?logger.Range = null, // Does not include TypeScript-specific syntax or "import()" - export_keyword: ?logger.Range = null, // Does not include TypeScript-specific syntax - top_level_await_keyword: ?logger.Range = null, + import_keyword: logger.Range = logger.Range.None, // Does not include TypeScript-specific syntax or "import()" + export_keyword: logger.Range = logger.Range.None, // Does not include TypeScript-specific syntax + top_level_await_keyword: logger.Range = logger.Range.None, // These are stored at the AST level instead of on individual AST nodes so // they can be manipulated efficiently without a full AST traversal - import_records: []ImportRecord = &([_]ImportRecord{}), + import_records: ImportRecord.List = .{}, hashbang: ?string = null, directive: ?string = null, url_for_css: ?string = null, - parts: []Part, - symbols: []Symbol = &([_]Symbol{}), - module_scope: ?Scope = null, + parts: Part.List = Part.List{}, + // This list may be mutated later, so we should store the capacity + symbols: Symbol.List = Symbol.List{}, + module_scope: Scope = Scope{}, // char_freq: *CharFreq, - exports_ref: ?Ref = null, + exports_ref: Ref = Ref.None, module_ref: ?Ref = null, wrapper_ref: ?Ref = null, require_ref: Ref = Ref.None, @@ -4583,21 +5279,38 @@ pub const Ast = struct { // These are used when bundling. They are filled in during the parser pass // since we already have to traverse the AST then anyway and the parser pass // is conveniently fully parallelized. - named_imports: NamedImports = undefined, - named_exports: NamedExports = undefined, + named_imports: NamedImports = NamedImports.init(bun.failing_allocator), + named_exports: NamedExports = NamedExports.init(bun.failing_allocator), export_star_import_records: []u32 = &([_]u32{}), + allocator: std.mem.Allocator, + top_level_symbols_to_parts: TopLevelSymbolToParts = .{}, + + commonjs_named_exports: CommonJSNamedExports = .{}, + + redirect_import_record_index: ?u32 = null, + + /// Only populated when bundling + platform: bun.options.Platform = .browser, + + pub const CommonJSNamedExport = struct { + loc_ref: LocRef, + needs_decl: bool = true, + }; + pub const CommonJSNamedExports = bun.StringArrayHashMapUnmanaged(CommonJSNamedExport); + pub const NamedImports = std.ArrayHashMap(Ref, NamedImport, RefHashCtx, true); pub const NamedExports = bun.StringArrayHashMap(NamedExport); pub fn initTest(parts: []Part) Ast { return Ast{ - .parts = parts, + .parts = Part.List.init(parts), + .allocator = bun.default_allocator, .runtime_imports = .{}, }; } - pub const empty = Ast{ .parts = &[_]Part{}, .runtime_imports = undefined }; + pub const empty = Ast{ .parts = Part.List{}, .runtime_imports = .{}, .allocator = bun.default_allocator }; pub fn toJSON(self: *const Ast, _: std.mem.Allocator, stream: anytype) !void { const opts = std.json.StringifyOptions{ .whitespace = std.json.StringifyOptions.Whitespace{ @@ -4609,16 +5322,16 @@ pub const Ast = struct { /// Do not call this if it wasn't globally allocated! pub fn deinit(this: *Ast) void { // TODO: assert mimalloc-owned memory - if (this.parts.len > 0) bun.default_allocator.free(this.parts); + if (this.parts.len > 0) this.parts.deinitWithAllocator(bun.default_allocator); if (this.externals.len > 0) bun.default_allocator.free(this.externals); - if (this.symbols.len > 0) bun.default_allocator.free(this.symbols); - if (this.import_records.len > 0) bun.default_allocator.free(this.import_records); + if (this.symbols.len > 0) this.symbols.deinitWithAllocator(bun.default_allocator); + if (this.import_records.len > 0) this.import_records.deinitWithAllocator(bun.default_allocator); } }; pub const Span = struct { - text: string, - range: logger.Range, + text: string = "", + range: logger.Range = .{}, }; pub const ExportsKind = enum { @@ -4646,7 +5359,11 @@ pub const ExportsKind = enum { // "exports") with getters for the export names. All named imports to this // module are allowed. Direct named imports reference the corresponding export // directly. Other imports go through property accesses on "exports". - esm_with_dyn, + esm_with_dynamic_fallback, + + pub fn isDynamic(self: ExportsKind) bool { + return self == .esm_with_dynamic_fallback or self == .cjs; + } pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void { return try std.json.stringify(@tagName(self), opts, o); @@ -4656,11 +5373,103 @@ pub const ExportsKind = enum { pub const DeclaredSymbol = struct { ref: Ref, is_top_level: bool = false, + + pub const List = struct { + entries: bun.MultiArrayList(DeclaredSymbol) = .{}, + + pub fn refs(this: *const List) []Ref { + return this.entries.items(.ref); + } + + pub fn toOwnedSlice(this: *List) List { + var new = this.*; + + this.* = .{}; + return new; + } + + pub fn clone(this: *const List, allocator: std.mem.Allocator) !List { + return List{ .entries = try this.entries.clone(allocator) }; + } + + pub inline fn len(this: List) usize { + return this.entries.len; + } + + pub fn append(this: *List, allocator: std.mem.Allocator, entry: DeclaredSymbol) !void { + try this.ensureUnusedCapacity(allocator, 1); + this.appendAssumeCapacity(entry); + } + + pub fn appendList(this: *List, allocator: std.mem.Allocator, other: List) !void { + try this.ensureUnusedCapacity(allocator, other.len()); + this.appendListAssumeCapacity(other); + } + + pub fn appendListAssumeCapacity(this: *List, other: List) void { + this.entries.appendListAssumeCapacity(other.entries); + } + + pub fn appendAssumeCapacity(this: *List, entry: DeclaredSymbol) void { + this.entries.appendAssumeCapacity(entry); + } + + pub fn ensureTotalCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { + try this.entries.ensureTotalCapacity(allocator, count); + } + + pub fn ensureUnusedCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void { + try this.entries.ensureUnusedCapacity(allocator, count); + } + + pub fn clearRetainingCapacity(this: *List) void { + this.entries.clearRetainingCapacity(); + } + + pub fn deinit(this: *List, allocator: std.mem.Allocator) void { + this.entries.deinit(allocator); + } + + pub fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !List { + var entries = bun.MultiArrayList(DeclaredSymbol){}; + try entries.ensureUnusedCapacity(allocator, capacity); + return List{ .entries = entries }; + } + + pub fn fromSlice(allocator: std.mem.Allocator, entries: []const DeclaredSymbol) !List { + var this = try List.initCapacity(allocator, entries.len); + errdefer this.deinit(allocator); + for (entries) |entry| { + this.appendAssumeCapacity(entry); + } + + return this; + } + }; + + fn forEachTopLevelSymbolWithType(decls: *List, comptime Ctx: type, ctx: Ctx, comptime Fn: fn (Ctx, Ref) void) void { + var entries = decls.entries.slice(); + const is_top_level = entries.items(.is_top_level); + const refs = entries.items(.ref); + + // TODO: SIMD + for (is_top_level, refs) |top, ref| { + if (top) { + @call(.always_inline, Fn, .{ ctx, ref }); + } + } + } + + pub fn forEachTopLevelSymbol(decls: *List, ctx: anytype, comptime Fn: anytype) void { + forEachTopLevelSymbolWithType(decls, @TypeOf(ctx), ctx, Fn); + } }; -pub const Dependency = packed struct { - source_index: u32 = 0, - part_index: u32 = 0, +pub const Dependency = struct { + source_index: Index = Index.invalid, + part_index: Index.Int = 0, + + pub const List = BabyList(Dependency); }; pub const ExprList = std.ArrayList(Expr); @@ -4673,24 +5482,27 @@ pub const BindingList = std.ArrayList(Binding); // shaking and can be assigned to separate chunks (i.e. output files) by code // splitting. pub const Part = struct { - stmts: []Stmt, + pub const ImportRecordIndices = BabyList(u32); + pub const List = BabyList(Part); + + stmts: []Stmt = &([_]Stmt{}), scopes: []*Scope = &([_]*Scope{}), // Each is an index into the file-level import record list - import_record_indices: []u32 = &([_]u32{}), + import_record_indices: ImportRecordIndices = .{}, // All symbols that are declared in this part. Note that a given symbol may // have multiple declarations, and so may end up being declared in multiple // parts (e.g. multiple "var" declarations with the same name). Also note // that this list isn't deduplicated and may contain duplicates. - declared_symbols: []DeclaredSymbol = &([_]DeclaredSymbol{}), + declared_symbols: DeclaredSymbol.List = .{}, // An estimate of the number of uses of all symbols used within this part. symbol_uses: SymbolUseMap = SymbolUseMap{}, // The indices of the other parts in this file that are needed if this part // is needed. - dependencies: []Dependency = &([_]Dependency{}), + dependencies: Dependency.List = .{}, // If true, this part can be removed if none of the declared symbols are // used. If the file containing this part is imported, then all parts that @@ -4708,6 +5520,8 @@ pub const Part = struct { tag: Tag = Tag.none, + valid_in_development: if (bun.Environment.allow_assert) bool else void = bun.DebugOnlyDefault(true), + pub const Tag = enum { none, jsx_import, @@ -4737,10 +5551,10 @@ pub const StmtOrExpr = union(enum) { pub const NamedImport = struct { // Parts within this file that use this import - local_parts_with_uses: []u32 = &([_]u32{}), + local_parts_with_uses: BabyList(u32) = BabyList(u32){}, alias: ?string, - alias_loc: ?logger.Loc, + alias_loc: ?logger.Loc = null, namespace_ref: ?Ref, import_record_index: u32, @@ -4760,7 +5574,7 @@ pub const NamedExport = struct { alias_loc: logger.Loc, }; -pub const StrictModeKind = enum(u7) { +pub const StrictModeKind = enum(u4) { sloppy_mode, explicit_strict_mode, implicit_strict_mode_import, @@ -4777,10 +5591,10 @@ pub const Scope = struct { id: usize = 0, kind: Kind = Kind.block, - parent: ?*Scope, - children: std.ArrayListUnmanaged(*Scope) = .{}, + parent: ?*Scope = null, + children: BabyList(*Scope) = .{}, members: MemberHashMap = .{}, - generated: std.ArrayListUnmanaged(Ref) = .{}, + generated: BabyList(Ref) = .{}, // This is used to store the ref of the label symbol for ScopeLabel scopes. label_ref: ?Ref = null, @@ -4798,6 +5612,8 @@ pub const Scope = struct { is_after_const_local_prefix: bool = false, + pub const NestedScopeMap = std.AutoArrayHashMap(u32, bun.BabyList(*Scope)); + pub fn getMemberHash(name: []const u8) u64 { return bun.StringHashMapContext.hash(.{}, name); } @@ -4900,10 +5716,12 @@ pub const Scope = struct { // "var foo; function foo() {}" // "function foo() {} var foo;" // "function *foo() {} function *foo() {}" but not "{ function *foo() {} function *foo() {} }" - if (Symbol.isKindHoistedOrFunction(new) and Symbol.isKindHoistedOrFunction(existing) and (scope.kind == .entry or scope.kind == .function_body or - (Symbol.isKindHoisted(new) and Symbol.isKindHoisted(existing)))) + if (Symbol.isKindHoistedOrFunction(new) and + Symbol.isKindHoistedOrFunction(existing) and + (scope.kind == .entry or scope.kind == .function_body or scope.kind == .function_args or + (new == existing and Symbol.isKindHoisted(existing)))) { - return .keep_existing; + return .replace_with_new; } // "get #foo() {} set #foo() {}" @@ -4958,7 +5776,7 @@ pub const Scope = struct { pub fn recursiveSetStrictMode(s: *Scope, kind: StrictModeKind) void { if (s.strict_mode == .sloppy_mode) { s.strict_mode = kind; - for (s.children.items) |child| { + for (s.children.slice()) |child| { child.recursiveSetStrictMode(kind); } } @@ -8337,6 +9155,116 @@ pub const Macro = struct { }; }; +pub const ASTMemoryAllocator = struct { + stack_allocator: std.heap.StackFallbackAllocator(8096) = undefined, + bump_allocator: std.mem.Allocator = undefined, + allocator: std.mem.Allocator, + previous: ?*ASTMemoryAllocator = null, + + pub fn push(this: *ASTMemoryAllocator) void { + if (Stmt.Data.Store.memory_allocator == this) { + return; + } + this.stack_allocator.fallback_allocator = this.allocator; + this.bump_allocator = this.stack_allocator.get(); + var prev = Stmt.Data.Store.memory_allocator; + if (this.previous) |other| { + other.previous = prev; + } else { + this.previous = prev; + } + Stmt.Data.Store.memory_allocator = this; + Expr.Data.Store.memory_allocator = this; + } + + pub fn pop(this: *ASTMemoryAllocator) void { + var prev = this.previous; + std.debug.assert(prev != this); + Stmt.Data.Store.memory_allocator = prev; + Expr.Data.Store.memory_allocator = prev; + this.previous = null; + } + + pub fn append(this: ASTMemoryAllocator, comptime ValueType: type, value: anytype) *ValueType { + const ptr = this.bump_allocator.create(ValueType) catch unreachable; + ptr.* = value; + return ptr; + } +}; + +pub const UseDirective = enum { + none, + @"use client", + @"use server", + + pub const Flags = struct { + is_client: bool = false, + is_server: bool = false, + }; + + pub fn isBoundary(this: UseDirective, other: UseDirective) bool { + if (this == other or other == .none) + return false; + + return true; + } + + pub fn boundering(this: UseDirective, other: UseDirective) ?UseDirective { + if (this == other or other == .none) + return null; + + return other; + } + + pub const EntryPoint = struct { + source_index: Index.Int, + use_directive: UseDirective, + }; + + pub const List = std.MultiArrayList(UseDirective.EntryPoint); + + // TODO: remove this, add an onModuleDirective() callback to the parser + pub fn parse(contents: []const u8) UseDirective { + const truncated = std.mem.trimLeft(u8, contents, " \t\n\r;"); + + if (truncated.len < "'use client';".len) + return .none; + + const directive_string = truncated[0.."'use client';".len].*; + + const first_quote = directive_string[0]; + const last_quote = directive_string[directive_string.len - 2]; + if (first_quote != last_quote or (first_quote != '"' and first_quote != '\'' and first_quote != '`')) + return .none; + + const unquoted = directive_string[1 .. directive_string.len - 2]; + + if (strings.eqlComptime( + unquoted, + "use client", + )) { + return .@"use client"; + } + + if (strings.eqlComptime( + unquoted, + "use server", + )) { + return .@"use server"; + } + + return .none; + } + + pub fn platform(this: UseDirective, default: bun.options.Platform) bun.options.Platform { + return switch (this) { + .none => default, + .@"use client" => .browser, + .@"use server" => .bun, + }; + } +}; + // test "Binding.init" { // var binding = Binding.alloc( // std.heap.page_allocator, @@ -8525,3 +9453,4 @@ pub const Macro = struct { // Stmt | 192 // STry | 384 // -- ESBuild bit sizes + |