mirror of
https://github.com/oven-sh/bun
synced 2026-02-04 07:58:54 +00:00
Compare commits
1 Commits
dylan/byte
...
jarred/spl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3748fd87e |
@@ -82,14 +82,14 @@ const CrossChunkDependencies = struct {
|
||||
if (chunk.content != .javascript) continue;
|
||||
|
||||
// Go over each part in this file that's marked for inclusion in this chunk
|
||||
const parts = deps.parts[source_index].slice();
|
||||
const parts: []const Part = deps.parts[source_index].slice();
|
||||
var import_records = deps.import_records[source_index].slice();
|
||||
const imports_to_bind = deps.imports_to_bind[source_index];
|
||||
const wrap = deps.flags[source_index].wrap;
|
||||
const wrapper_ref = deps.wrapper_refs[source_index];
|
||||
const _chunks = deps.chunks;
|
||||
|
||||
for (parts) |part| {
|
||||
for (parts) |*part| {
|
||||
if (!part.is_live)
|
||||
continue;
|
||||
|
||||
@@ -114,7 +114,7 @@ const CrossChunkDependencies = struct {
|
||||
// the same name should already be marked as all being in a single
|
||||
// chunk. In that case this will overwrite the same value below which
|
||||
// is fine.
|
||||
deps.symbols.assignChunkIndex(part.declared_symbols, @as(u32, @truncate(chunk_index)));
|
||||
deps.symbols.assignChunkIndex(&part.declared_symbols, @as(u32, @truncate(chunk_index)));
|
||||
|
||||
const used_refs = part.symbol_uses.keys();
|
||||
|
||||
|
||||
523
src/js_ast.zig
523
src/js_ast.zig
@@ -228,6 +228,8 @@ pub const ExprNodeList = BabyList(Expr);
|
||||
pub const StmtNodeList = []Stmt;
|
||||
pub const BindingNodeList = []Binding;
|
||||
|
||||
pub const Symbol = @import("./js_ast/Symbol.zig");
|
||||
|
||||
pub const ImportItemStatus = enum(u2) {
|
||||
none,
|
||||
/// The linker doesn't report import/export mismatch errors
|
||||
@@ -959,483 +961,6 @@ 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.
|
||||
original_name: []const u8,
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
use_count_estimate: u32 = 0,
|
||||
|
||||
/// 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,
|
||||
|
||||
did_keep_name: bool = true,
|
||||
|
||||
must_start_with_capital_letter_for_jsx: bool = false,
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
import_item_status: ImportItemStatus = ImportItemStatus.none,
|
||||
|
||||
/// --- 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,
|
||||
|
||||
/// Used in HMR to decide when live binding code is needed.
|
||||
has_been_assigned_to: bool = false,
|
||||
|
||||
comptime {
|
||||
bun.assert_eql(@sizeOf(Symbol), 88);
|
||||
bun.assert_eql(@alignOf(Symbol), @alignOf([]const u8));
|
||||
}
|
||||
|
||||
const invalid_chunk_index = std.math.maxInt(u32);
|
||||
pub const invalid_nested_scope_slot = std.math.maxInt(u32);
|
||||
|
||||
pub const SlotNamespace = enum {
|
||||
must_not_be_renamed,
|
||||
default,
|
||||
label,
|
||||
private_name,
|
||||
mangled_prop,
|
||||
|
||||
pub const CountsArray = std.EnumArray(SlotNamespace, u32);
|
||||
};
|
||||
|
||||
/// 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.tag != .invalid;
|
||||
}
|
||||
|
||||
pub const Kind = enum {
|
||||
/// An unbound symbol is one that isn't declared in the file it's referenced
|
||||
/// in. For example, using "window" without declaring it will be unbound.
|
||||
unbound,
|
||||
|
||||
/// This has special merging behavior. You're allowed to re-declare these
|
||||
/// symbols more than once in the same scope. These symbols are also hoisted
|
||||
/// out of the scope they are declared in to the closest containing function
|
||||
/// or module scope. These are the symbols with this kind:
|
||||
///
|
||||
/// - Function arguments
|
||||
/// - Function statements
|
||||
/// - Variables declared using "var"
|
||||
hoisted,
|
||||
hoisted_function,
|
||||
|
||||
/// There's a weird special case where catch variables declared using a simple
|
||||
/// identifier (i.e. not a binding pattern) block hoisted variables instead of
|
||||
/// becoming an error:
|
||||
///
|
||||
/// var e = 0;
|
||||
/// try { throw 1 } catch (e) {
|
||||
/// print(e) // 1
|
||||
/// var e = 2
|
||||
/// print(e) // 2
|
||||
/// }
|
||||
/// print(e) // 0 (since the hoisting stops at the catch block boundary)
|
||||
///
|
||||
/// However, other forms are still a syntax error:
|
||||
///
|
||||
/// try {} catch (e) { let e }
|
||||
/// try {} catch ({e}) { var e }
|
||||
///
|
||||
/// This symbol is for handling this weird special case.
|
||||
catch_identifier,
|
||||
|
||||
/// Generator and async functions are not hoisted, but still have special
|
||||
/// properties such as being able to overwrite previous functions with the
|
||||
/// same name
|
||||
generator_or_async_function,
|
||||
|
||||
/// This is the special "arguments" variable inside functions
|
||||
arguments,
|
||||
|
||||
/// Classes can merge with TypeScript namespaces.
|
||||
class,
|
||||
|
||||
/// A class-private identifier (i.e. "#foo").
|
||||
private_field,
|
||||
private_method,
|
||||
private_get,
|
||||
private_set,
|
||||
private_get_set_pair,
|
||||
private_static_field,
|
||||
private_static_method,
|
||||
private_static_get,
|
||||
private_static_set,
|
||||
private_static_get_set_pair,
|
||||
|
||||
/// Labels are in their own namespace
|
||||
label,
|
||||
|
||||
/// TypeScript enums can merge with TypeScript namespaces and other TypeScript
|
||||
/// enums.
|
||||
ts_enum,
|
||||
|
||||
/// TypeScript namespaces can merge with classes, functions, TypeScript enums,
|
||||
/// and other TypeScript namespaces.
|
||||
ts_namespace,
|
||||
|
||||
/// In TypeScript, imports are allowed to silently collide with symbols within
|
||||
/// the module. Presumably this is because the imports may be type-only.
|
||||
/// Import statement namespace references should NOT have this set.
|
||||
import,
|
||||
|
||||
/// Assigning to a "const" symbol will throw a TypeError at runtime
|
||||
constant,
|
||||
|
||||
// CSS identifiers that are renamed to be unique to the file they are in
|
||||
local_css,
|
||||
|
||||
/// This annotates all other symbols that don't have special behavior.
|
||||
other,
|
||||
|
||||
pub fn jsonStringify(self: @This(), writer: anytype) !void {
|
||||
return try writer.write(@tagName(self));
|
||||
}
|
||||
|
||||
pub inline fn isPrivate(kind: Symbol.Kind) bool {
|
||||
return @intFromEnum(kind) >= @intFromEnum(Symbol.Kind.private_field) and @intFromEnum(kind) <= @intFromEnum(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
|
||||
// a hash. This representation also makes it trivial to quickly merge symbol
|
||||
// maps from multiple files together. Each file only generates symbols in a
|
||||
// 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: NestedList = .{},
|
||||
|
||||
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(i),
|
||||
.inner_index = @truncate(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.at(ref.sourceIndex()).mut(ref.innerIndex());
|
||||
}
|
||||
|
||||
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.at(ref.sourceIndex()).at(ref.innerIndex());
|
||||
}
|
||||
|
||||
pub fn init(sourceCount: usize, allocator: std.mem.Allocator) !Map {
|
||||
const symbols_for_source: NestedList = NestedList.init(try allocator.alloc([]Symbol, sourceCount));
|
||||
return Map{ .symbols_for_source = symbols_for_source };
|
||||
}
|
||||
|
||||
pub fn initWithOneList(list: List) Map {
|
||||
const baby_list = BabyList(List).init((&list)[0..1]);
|
||||
return initList(baby_list);
|
||||
}
|
||||
|
||||
pub fn initList(list: NestedList) Map {
|
||||
return Map{ .symbols_for_source = list };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
pub fn getWithLinkConst(symbols: *Map, ref: Ref) ?*const Symbol {
|
||||
var symbol: *const Symbol = symbols.getConst(ref) orelse return null;
|
||||
if (symbol.hasLink()) {
|
||||
return symbols.getConst(symbol.link) orelse symbol;
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
pub fn followAll(symbols: *Map) void {
|
||||
const trace = bun.perf.trace("Symbols.followAll");
|
||||
defer trace.end();
|
||||
for (symbols.symbols_for_source.slice()) |list| {
|
||||
for (list.slice()) |*symbol| {
|
||||
if (!symbol.hasLink()) continue;
|
||||
symbol.link = follow(symbols, symbol.link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to followSymbols in esbuild
|
||||
pub fn follow(symbols: *const Map, ref: Ref) Ref {
|
||||
var symbol = symbols.get(ref) orelse return ref;
|
||||
if (!symbol.hasLink()) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
const link = follow(symbols, symbol.link);
|
||||
|
||||
if (!symbol.link.eql(link)) {
|
||||
symbol.link = link;
|
||||
}
|
||||
|
||||
return link;
|
||||
}
|
||||
};
|
||||
|
||||
pub inline fn isHoisted(self: *const Symbol) bool {
|
||||
return Symbol.isKindHoisted(self.kind);
|
||||
}
|
||||
};
|
||||
|
||||
pub const OptionalChain = enum(u1) {
|
||||
/// "a?.b"
|
||||
start,
|
||||
@@ -7634,12 +7159,46 @@ pub const DeclaredSymbol = struct {
|
||||
}
|
||||
};
|
||||
|
||||
fn forEachTopLevelSymbolWithType(decls: *List, comptime Ctx: type, ctx: Ctx, comptime Fn: fn (Ctx, Ref) void) void {
|
||||
fn forEachTopLevelSymbolWithType(decls: *const 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);
|
||||
var is_top_level = entries.items(.is_top_level);
|
||||
var refs = entries.items(.ref);
|
||||
|
||||
// Scan 64 elements at a time for top-level declared symbols.
|
||||
if (comptime bun.Environment.enableSIMD) {
|
||||
const ChunkVector = @Vector(16, u1);
|
||||
const BoolChunkVector = @Vector(16, bool);
|
||||
|
||||
const is_top_level_len_64 = (is_top_level.len - (is_top_level.len % 64)) / 64;
|
||||
|
||||
for (0..is_top_level_len_64) |_| {
|
||||
const current = is_top_level[0..64];
|
||||
const current_refs = refs[0..64];
|
||||
is_top_level = is_top_level[64..];
|
||||
refs = refs[64..];
|
||||
|
||||
const chunks: [4]u16 align(64) = [_]u16{
|
||||
@bitCast(@as(ChunkVector, @bitCast(@as(BoolChunkVector, current[0..16].*)))),
|
||||
@bitCast(@as(ChunkVector, @bitCast(@as(BoolChunkVector, current[16..32].*)))),
|
||||
@bitCast(@as(ChunkVector, @bitCast(@as(BoolChunkVector, current[32..48].*)))),
|
||||
@bitCast(@as(ChunkVector, @bitCast(@as(BoolChunkVector, current[48..64].*)))),
|
||||
};
|
||||
|
||||
const mask: u64 = (@as(u64, chunks[0])) |
|
||||
(@as(u64, chunks[1]) << 16) |
|
||||
(@as(u64, chunks[2]) << 32) |
|
||||
(@as(u64, chunks[3]) << 48);
|
||||
|
||||
if (mask != 0) {
|
||||
for (0..64) |i| {
|
||||
if ((mask >> @intCast(i)) & 1 != 0) {
|
||||
@call(bun.callmod_inline, Fn, .{ ctx, current_refs[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: SIMD
|
||||
for (is_top_level, refs) |top, ref| {
|
||||
if (top) {
|
||||
@call(bun.callmod_inline, Fn, .{ ctx, ref });
|
||||
@@ -7647,7 +7206,7 @@ pub const DeclaredSymbol = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn forEachTopLevelSymbol(decls: *List, ctx: anytype, comptime Fn: anytype) void {
|
||||
pub fn forEachTopLevelSymbol(decls: *const List, ctx: anytype, comptime Fn: anytype) void {
|
||||
forEachTopLevelSymbolWithType(decls, @TypeOf(ctx), ctx, Fn);
|
||||
}
|
||||
};
|
||||
|
||||
484
src/js_ast/Symbol.zig
Normal file
484
src/js_ast/Symbol.zig
Normal file
@@ -0,0 +1,484 @@
|
||||
const Symbol = @This();
|
||||
|
||||
/// 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: []const u8,
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
use_count_estimate: u32 = 0,
|
||||
|
||||
/// 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,
|
||||
|
||||
did_keep_name: bool = true,
|
||||
|
||||
must_start_with_capital_letter_for_jsx: bool = false,
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
import_item_status: ImportItemStatus = ImportItemStatus.none,
|
||||
|
||||
/// --- 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,
|
||||
|
||||
/// Used in HMR to decide when live binding code is needed.
|
||||
has_been_assigned_to: bool = false,
|
||||
|
||||
comptime {
|
||||
bun.assert_eql(@sizeOf(Symbol), 88);
|
||||
bun.assert_eql(@alignOf(Symbol), @alignOf([]const u8));
|
||||
}
|
||||
|
||||
const invalid_chunk_index = std.math.maxInt(u32);
|
||||
pub const invalid_nested_scope_slot = std.math.maxInt(u32);
|
||||
|
||||
pub const SlotNamespace = enum {
|
||||
must_not_be_renamed,
|
||||
default,
|
||||
label,
|
||||
private_name,
|
||||
mangled_prop,
|
||||
|
||||
pub const CountsArray = std.EnumArray(SlotNamespace, u32);
|
||||
};
|
||||
|
||||
/// 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.tag != .invalid;
|
||||
}
|
||||
|
||||
pub const Kind = enum {
|
||||
/// An unbound symbol is one that isn't declared in the file it's referenced
|
||||
/// in. For example, using "window" without declaring it will be unbound.
|
||||
unbound,
|
||||
|
||||
/// This has special merging behavior. You're allowed to re-declare these
|
||||
/// symbols more than once in the same scope. These symbols are also hoisted
|
||||
/// out of the scope they are declared in to the closest containing function
|
||||
/// or module scope. These are the symbols with this kind:
|
||||
///
|
||||
/// - Function arguments
|
||||
/// - Function statements
|
||||
/// - Variables declared using "var"
|
||||
hoisted,
|
||||
hoisted_function,
|
||||
|
||||
/// There's a weird special case where catch variables declared using a simple
|
||||
/// identifier (i.e. not a binding pattern) block hoisted variables instead of
|
||||
/// becoming an error:
|
||||
///
|
||||
/// var e = 0;
|
||||
/// try { throw 1 } catch (e) {
|
||||
/// print(e) // 1
|
||||
/// var e = 2
|
||||
/// print(e) // 2
|
||||
/// }
|
||||
/// print(e) // 0 (since the hoisting stops at the catch block boundary)
|
||||
///
|
||||
/// However, other forms are still a syntax error:
|
||||
///
|
||||
/// try {} catch (e) { let e }
|
||||
/// try {} catch ({e}) { var e }
|
||||
///
|
||||
/// This symbol is for handling this weird special case.
|
||||
catch_identifier,
|
||||
|
||||
/// Generator and async functions are not hoisted, but still have special
|
||||
/// properties such as being able to overwrite previous functions with the
|
||||
/// same name
|
||||
generator_or_async_function,
|
||||
|
||||
/// This is the special "arguments" variable inside functions
|
||||
arguments,
|
||||
|
||||
/// Classes can merge with TypeScript namespaces.
|
||||
class,
|
||||
|
||||
/// A class-private identifier (i.e. "#foo").
|
||||
private_field,
|
||||
private_method,
|
||||
private_get,
|
||||
private_set,
|
||||
private_get_set_pair,
|
||||
private_static_field,
|
||||
private_static_method,
|
||||
private_static_get,
|
||||
private_static_set,
|
||||
private_static_get_set_pair,
|
||||
|
||||
/// Labels are in their own namespace
|
||||
label,
|
||||
|
||||
/// TypeScript enums can merge with TypeScript namespaces and other TypeScript
|
||||
/// enums.
|
||||
ts_enum,
|
||||
|
||||
/// TypeScript namespaces can merge with classes, functions, TypeScript enums,
|
||||
/// and other TypeScript namespaces.
|
||||
ts_namespace,
|
||||
|
||||
/// In TypeScript, imports are allowed to silently collide with symbols within
|
||||
/// the module. Presumably this is because the imports may be type-only.
|
||||
/// Import statement namespace references should NOT have this set.
|
||||
import,
|
||||
|
||||
/// Assigning to a "const" symbol will throw a TypeError at runtime
|
||||
constant,
|
||||
|
||||
// CSS identifiers that are renamed to be unique to the file they are in
|
||||
local_css,
|
||||
|
||||
/// This annotates all other symbols that don't have special behavior.
|
||||
other,
|
||||
|
||||
pub fn jsonStringify(self: @This(), writer: anytype) !void {
|
||||
return try writer.write(@tagName(self));
|
||||
}
|
||||
|
||||
pub inline fn isPrivate(kind: Symbol.Kind) bool {
|
||||
return @intFromEnum(kind) >= @intFromEnum(Symbol.Kind.private_field) and @intFromEnum(kind) <= @intFromEnum(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
|
||||
// a hash. This representation also makes it trivial to quickly merge symbol
|
||||
// maps from multiple files together. Each file only generates symbols in a
|
||||
// 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: NestedList = .{},
|
||||
|
||||
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(i),
|
||||
.inner_index = @truncate(inner_index),
|
||||
.tag = .symbol,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assignChunkIndex(this: *Map, decls: *const DeclaredSymbol.List, chunk_index: u32) void {
|
||||
const Iterator = struct {
|
||||
map: *Map,
|
||||
chunk_index: u32,
|
||||
|
||||
pub fn next(self: @This(), ref: Ref) void {
|
||||
const symbol = self.map.get(ref).?;
|
||||
symbol.chunk_index = self.chunk_index;
|
||||
}
|
||||
};
|
||||
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.at(ref.sourceIndex()).mut(ref.innerIndex());
|
||||
}
|
||||
|
||||
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.at(ref.sourceIndex()).at(ref.innerIndex());
|
||||
}
|
||||
|
||||
pub fn init(sourceCount: usize, allocator: std.mem.Allocator) !Map {
|
||||
const symbols_for_source: NestedList = NestedList.init(try allocator.alloc([]Symbol, sourceCount));
|
||||
return Map{ .symbols_for_source = symbols_for_source };
|
||||
}
|
||||
|
||||
pub fn initWithOneList(list: List) Map {
|
||||
const baby_list = BabyList(List).init((&list)[0..1]);
|
||||
return initList(baby_list);
|
||||
}
|
||||
|
||||
pub fn initList(list: NestedList) Map {
|
||||
return Map{ .symbols_for_source = list };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
pub fn getWithLinkConst(symbols: *Map, ref: Ref) ?*const Symbol {
|
||||
var symbol: *const Symbol = symbols.getConst(ref) orelse return null;
|
||||
if (symbol.hasLink()) {
|
||||
return symbols.getConst(symbol.link) orelse symbol;
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
pub fn followAll(symbols: *Map) void {
|
||||
const trace = bun.perf.trace("Symbols.followAll");
|
||||
defer trace.end();
|
||||
for (symbols.symbols_for_source.slice()) |list| {
|
||||
for (list.slice()) |*symbol| {
|
||||
if (!symbol.hasLink()) continue;
|
||||
symbol.link = follow(symbols, symbol.link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to followSymbols in esbuild
|
||||
pub fn follow(symbols: *const Map, ref: Ref) Ref {
|
||||
var symbol = symbols.get(ref) orelse return ref;
|
||||
if (!symbol.hasLink()) {
|
||||
return ref;
|
||||
}
|
||||
|
||||
const link = follow(symbols, symbol.link);
|
||||
|
||||
if (!symbol.link.eql(link)) {
|
||||
symbol.link = link;
|
||||
}
|
||||
|
||||
return link;
|
||||
}
|
||||
};
|
||||
|
||||
pub inline fn isHoisted(self: *const Symbol) bool {
|
||||
return Symbol.isKindHoisted(self.kind);
|
||||
}
|
||||
|
||||
const bun = @import("bun");
|
||||
const std = @import("std");
|
||||
const Output = bun.Output;
|
||||
const js_ast = bun.js_ast;
|
||||
const G = js_ast.G;
|
||||
const DeclaredSymbol = js_ast.DeclaredSymbol;
|
||||
const Ref = js_ast.Ref;
|
||||
const BabyList = js_ast.BabyList;
|
||||
const ImportItemStatus = js_ast.ImportItemStatus;
|
||||
Reference in New Issue
Block a user