Files
bun.sh/src/semver/SemverString.zig
pfg 05d0475c6c Update to zig 0.15.2 (#24204)
Fixes ENG-21287

Build times, from `bun run build && echo '//' >> src/main.zig && time
bun run build`

|Platform|0.14.1|0.15.2|Speedup|
|-|-|-|-|
|macos debug asan|126.90s|106.27s|1.19x|
|macos debug noasan|60.62s|50.85s|1.19x|
|linux debug asan|292.77s|241.45s|1.21x|
|linux debug noasan|146.58s|130.94s|1.12x|
|linux debug use_llvm=false|n/a|78.27s|1.87x|
|windows debug asan|177.13s|142.55s|1.24x|

Runtime performance:

- next build memory usage may have gone up by 5%. Otherwise seems the
same. Some code with writers may have gotten slower, especially one
instance of a counting writer and a few instances of unbuffered writers
that now have vtable overhead.
- File size reduced by 800kb (from 100.2mb to 99.4mb)

Improvements:

- `@export` hack is no longer needed for watch
- native x86_64 backend for linux builds faster. to use it, set use_llvm
false and no_link_obj false. also set `ASAN_OPTIONS=detect_leaks=0`
otherwise it will spam the output with tens of thousands of lines of
debug info errors. may need to use the zig lldb fork for debugging.
- zig test-obj, which we will be able to use for zig unit tests

Still an issue:

- false 'dependency loop' errors remain in watch mode
- watch mode crashes observed

Follow-up:

- [ ] search `comptime Writer: type` and `comptime W: type` and remove
- [ ] remove format_mode in our zig fork
- [ ] remove deprecated.zig autoFormatLabelFallback
- [ ] remove deprecated.zig autoFormatLabel
- [ ] remove deprecated.BufferedWriter and BufferedReader
- [ ] remove override_no_export_cpp_apis as it is no longer needed
- [ ] css Parser(W) -> Parser, and remove all the comptime writer: type
params
- [ ] remove deprecated writer fully

Files that add lines:

```
649     src/deprecated.zig
167     scripts/pack-codegen-for-zig-team.ts
54      scripts/cleartrace-impl.js
46      scripts/cleartrace.ts
43      src/windows.zig
18      src/fs.zig
17      src/bun.js/ConsoleObject.zig
16      src/output.zig
12      src/bun.js/test/debug.zig
12      src/bun.js/node/node_fs.zig
8       src/env_loader.zig
7       src/css/printer.zig
7       src/cli/init_command.zig
7       src/bun.js/node.zig
6       src/string/escapeRegExp.zig
6       src/install/PnpmMatcher.zig
5       src/bun.js/webcore/Blob.zig
4       src/crash_handler.zig
4       src/bun.zig
3       src/install/lockfile/bun.lock.zig
3       src/cli/update_interactive_command.zig
3       src/cli/pack_command.zig
3       build.zig
2       src/Progress.zig
2       src/install/lockfile/lockfile_json_stringify_for_debugging.zig
2       src/css/small_list.zig
2       src/bun.js/webcore/prompt.zig
1       test/internal/ban-words.test.ts
1       test/internal/ban-limits.json
1       src/watcher/WatcherTrace.zig
1       src/transpiler.zig
1       src/shell/builtin/cp.zig
1       src/js_printer.zig
1       src/io/PipeReader.zig
1       src/install/bin.zig
1       src/css/selectors/selector.zig
1       src/cli/run_command.zig
1       src/bun.js/RuntimeTranspilerStore.zig
1       src/bun.js/bindings/JSRef.zig
1       src/bake/DevServer.zig
```

Files that remove lines:

```
-1      src/test/recover.zig
-1      src/sql/postgres/SocketMonitor.zig
-1      src/sql/mysql/MySQLRequestQueue.zig
-1      src/sourcemap/CodeCoverage.zig
-1      src/css/values/color_js.zig
-1      src/compile_target.zig
-1      src/bundler/linker_context/convertStmtsForChunk.zig
-1      src/bundler/bundle_v2.zig
-1      src/bun.js/webcore/blob/read_file.zig
-1      src/ast/base.zig
-2      src/sql/postgres/protocol/ArrayList.zig
-2      src/shell/builtin/mkdir.zig
-2      src/install/PackageManager/patchPackage.zig
-2      src/install/PackageManager/PackageManagerDirectories.zig
-2      src/fmt.zig
-2      src/css/declaration.zig
-2      src/css/css_parser.zig
-2      src/collections/baby_list.zig
-2      src/bun.js/bindings/ZigStackFrame.zig
-2      src/ast/E.zig
-3      src/StandaloneModuleGraph.zig
-3      src/deps/picohttp.zig
-3      src/deps/libuv.zig
-3      src/btjs.zig
-4      src/threading/Futex.zig
-4      src/shell/builtin/touch.zig
-4      src/meta.zig
-4      src/install/lockfile.zig
-4      src/css/selectors/parser.zig
-5      src/shell/interpreter.zig
-5      src/css/error.zig
-5      src/bun.js/web_worker.zig
-5      src/bun.js.zig
-6      src/cli/test_command.zig
-6      src/bun.js/VirtualMachine.zig
-6      src/bun.js/uuid.zig
-6      src/bun.js/bindings/JSValue.zig
-9      src/bun.js/test/pretty_format.zig
-9      src/bun.js/api/BunObject.zig
-14     src/install/install_binding.zig
-14     src/fd.zig
-14     src/bun.js/node/path.zig
-14     scripts/pack-codegen-for-zig-team.sh
-17     src/bun.js/test/diff_format.zig
```

`git diff --numstat origin/main...HEAD | awk '{ print ($1-$2)"\t"$3 }' |
sort -rn`

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Meghan Denny <meghan@bun.com>
Co-authored-by: tayor.fish <contact@taylor.fish>
2025-11-10 14:38:26 -08:00

666 lines
23 KiB
Zig

/// String type that stores either an offset/length into an external buffer or a string inline directly
pub const String = extern struct {
pub const max_inline_len: usize = 8;
/// This is three different types of string.
/// 1. Empty string. If it's all zeroes, then it's an empty string.
/// 2. If the final bit is set, then it's a string that is stored inline.
/// 3. If the final bit is not set, then it's a string that is stored in an external buffer.
bytes: [max_inline_len]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 },
pub const empty: String = .{};
/// Create an inline string
pub fn from(comptime inlinable_buffer: []const u8) String {
comptime {
if (inlinable_buffer.len > max_inline_len or
inlinable_buffer.len == max_inline_len and
inlinable_buffer[max_inline_len - 1] >= 0x80)
{
@compileError("string constant too long to be inlined");
}
}
return String.init(inlinable_buffer, inlinable_buffer);
}
pub const Buf = struct {
bytes: *std.ArrayListUnmanaged(u8),
allocator: std.mem.Allocator,
pool: *Builder.StringPool,
pub fn init(lockfile: *const Lockfile) Buf {
return .{
.bytes = &lockfile.buffers.string_bytes,
.allocator = lockfile.allocator,
.pool = &lockfile.string_pool,
};
}
pub fn append(this: *Buf, str: string) OOM!String {
if (canInline(str)) {
return String.initInline(str);
}
const hash = Builder.stringHash(str);
const entry = try this.pool.getOrPut(hash);
if (entry.found_existing) {
return entry.value_ptr.*;
}
// new entry
const new = try String.initAppend(this.allocator, this.bytes, str);
entry.value_ptr.* = new;
return new;
}
pub fn appendWithHash(this: *Buf, str: string, hash: u64) OOM!String {
if (canInline(str)) {
return initInline(str);
}
const entry = try this.pool.getOrPut(hash);
if (entry.found_existing) {
return entry.value_ptr.*;
}
// new entry
const new = try String.initAppend(this.allocator, this.bytes, str);
entry.value_ptr.* = new;
return new;
}
pub fn appendExternal(this: *Buf, str: string) OOM!ExternalString {
const hash = Builder.stringHash(str);
if (canInline(str)) {
return .{
.value = String.initInline(str),
.hash = hash,
};
}
const entry = try this.pool.getOrPut(hash);
if (entry.found_existing) {
return .{
.value = entry.value_ptr.*,
.hash = hash,
};
}
const new = try String.initAppend(this.allocator, this.bytes, str);
entry.value_ptr.* = new;
return .{
.value = new,
.hash = hash,
};
}
pub fn appendExternalWithHash(this: *Buf, str: string, hash: u64) OOM!ExternalString {
if (canInline(str)) {
return .{
.value = initInline(str),
.hash = hash,
};
}
const entry = try this.pool.getOrPut(hash);
if (entry.found_existing) {
return .{
.value = entry.value_ptr.*,
.hash = hash,
};
}
const new = try String.initAppend(this.allocator, this.bytes, str);
entry.value_ptr.* = new;
return .{
.value = new,
.hash = hash,
};
}
};
pub const Tag = enum {
small,
big,
};
pub inline fn fmt(self: *const String, buf: []const u8) Formatter {
return Formatter{
.buf = buf,
.str = self,
};
}
pub const Formatter = struct {
str: *const String,
buf: string,
pub fn format(formatter: Formatter, writer: *std.Io.Writer) std.Io.Writer.Error!void {
const str = formatter.str;
try writer.writeAll(str.slice(formatter.buf));
}
};
/// Escapes for json. Defaults to quoting the string.
pub inline fn fmtJson(self: *const String, buf: []const u8, opts: JsonFormatter.Options) JsonFormatter {
return .{
.buf = buf,
.str = self,
.opts = opts,
};
}
pub const JsonFormatter = struct {
str: *const String,
buf: string,
opts: Options,
pub const Options = struct {
quote: bool = true,
};
pub fn format(formatter: JsonFormatter, writer: *std.Io.Writer) std.Io.Writer.Error!void {
try writer.print("{f}", .{bun.fmt.formatJSONStringUTF8(formatter.str.slice(formatter.buf), .{ .quote = formatter.opts.quote })});
}
};
pub inline fn fmtStorePath(self: *const String, buf: []const u8) StorePathFormatter {
return .{
.buf = buf,
.str = self,
};
}
pub const StorePathFormatter = struct {
str: *const String,
buf: string,
pub fn format(this: StorePathFormatter, writer: *std.Io.Writer) std.Io.Writer.Error!void {
for (this.str.slice(this.buf)) |c| {
const n = switch (c) {
'/' => '+',
'\\' => '+',
':' => '+',
'#' => '+',
else => c,
};
try writer.writeByte(n);
}
}
};
pub fn Sorter(comptime direction: enum { asc, desc }) type {
return struct {
lhs_buf: []const u8,
rhs_buf: []const u8,
pub fn lessThan(this: @This(), lhs: String, rhs: String) bool {
return lhs.order(&rhs, this.lhs_buf, this.rhs_buf) == if (comptime direction == .asc) .lt else .gt;
}
};
}
pub inline fn order(
lhs: *const String,
rhs: *const String,
lhs_buf: []const u8,
rhs_buf: []const u8,
) std.math.Order {
return strings.order(lhs.slice(lhs_buf), rhs.slice(rhs_buf));
}
pub inline fn canInline(buf: []const u8) bool {
return switch (buf.len) {
0...max_inline_len - 1 => true,
max_inline_len => buf[max_inline_len - 1] & 0x80 == 0,
else => false,
};
}
pub inline fn isInline(this: String) bool {
return this.bytes[max_inline_len - 1] & 0x80 == 0;
}
pub inline fn sliced(this: *const String, buf: []const u8) SlicedString {
return if (this.isInline())
SlicedString.init(this.slice(""), this.slice(""))
else
SlicedString.init(buf, this.slice(buf));
}
// https://en.wikipedia.org/wiki/Intel_5-level_paging
// https://developer.arm.com/documentation/101811/0101/Address-spaces-in-AArch64#:~:text=0%2DA%2C%20the%20maximum%20size,2%2DA.
// X64 seems to need some of the pointer bits
const max_addressable_space = u63;
comptime {
if (@sizeOf(usize) != 8) {
@compileError("This code needs to be updated for non-64-bit architectures");
}
}
pub const HashContext = struct {
arg_buf: []const u8,
existing_buf: []const u8,
pub fn eql(ctx: HashContext, arg: String, existing: String) bool {
return arg.eql(existing, ctx.arg_buf, ctx.existing_buf);
}
pub fn hash(ctx: HashContext, arg: String) u64 {
const str = arg.slice(ctx.arg_buf);
return bun.hash(str);
}
};
pub fn hashContext(l_lockfile: *Lockfile, r_lockfile: ?*Lockfile) HashContext {
return .{
.arg_buf = l_lockfile.buffers.string_bytes.items,
.existing_buf = if (r_lockfile) |r| r.buffers.string_bytes.items else l_lockfile.buffers.string_bytes.items,
};
}
pub const ArrayHashContext = struct {
arg_buf: []const u8,
existing_buf: []const u8,
pub fn eql(ctx: ArrayHashContext, arg: String, existing: String, _: usize) bool {
return arg.eql(existing, ctx.arg_buf, ctx.existing_buf);
}
pub fn hash(ctx: ArrayHashContext, arg: String) u32 {
const str = arg.slice(ctx.arg_buf);
return @as(u32, @truncate(bun.hash(str)));
}
};
pub fn arrayHashContext(l_lockfile: *const Lockfile, r_lockfile: ?*const Lockfile) ArrayHashContext {
return .{
.arg_buf = l_lockfile.buffers.string_bytes.items,
.existing_buf = if (r_lockfile) |r| r.buffers.string_bytes.items else l_lockfile.buffers.string_bytes.items,
};
}
pub fn init(
buf: string,
in: string,
) String {
return switch (in.len) {
0 => String{},
1 => String{ .bytes = .{ in[0], 0, 0, 0, 0, 0, 0, 0 } },
2 => String{ .bytes = .{ in[0], in[1], 0, 0, 0, 0, 0, 0 } },
3 => String{ .bytes = .{ in[0], in[1], in[2], 0, 0, 0, 0, 0 } },
4 => String{ .bytes = .{ in[0], in[1], in[2], in[3], 0, 0, 0, 0 } },
5 => String{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], 0, 0, 0 } },
6 => String{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], 0, 0 } },
7 => String{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], in[6], 0 } },
max_inline_len =>
// If they use the final bit, then it's a big string.
// This should only happen for non-ascii strings that are exactly 8 bytes.
// so that's an edge-case
if ((in[max_inline_len - 1]) >= 128)
@as(String, @bitCast((@as(
u64,
0,
) | @as(
u64,
@as(
max_addressable_space,
@truncate(@as(
u64,
@bitCast(Pointer.init(buf, in)),
)),
),
)) | 1 << 63))
else
String{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7] } },
else => @as(
String,
@bitCast((@as(
u64,
0,
) | @as(
u64,
@as(
max_addressable_space,
@truncate(@as(
u64,
@bitCast(Pointer.init(buf, in)),
)),
),
)) | 1 << 63),
),
};
}
pub fn initInline(
in: string,
) String {
bun.assertWithLocation(canInline(in), @src());
return switch (in.len) {
0 => .{},
1 => .{ .bytes = .{ in[0], 0, 0, 0, 0, 0, 0, 0 } },
2 => .{ .bytes = .{ in[0], in[1], 0, 0, 0, 0, 0, 0 } },
3 => .{ .bytes = .{ in[0], in[1], in[2], 0, 0, 0, 0, 0 } },
4 => .{ .bytes = .{ in[0], in[1], in[2], in[3], 0, 0, 0, 0 } },
5 => .{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], 0, 0, 0 } },
6 => .{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], 0, 0 } },
7 => .{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], in[6], 0 } },
8 => .{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7] } },
else => unreachable,
};
}
pub fn initAppendIfNeeded(
allocator: std.mem.Allocator,
buf: *std.ArrayListUnmanaged(u8),
in: string,
) OOM!String {
return switch (in.len) {
0 => .{},
1 => .{ .bytes = .{ in[0], 0, 0, 0, 0, 0, 0, 0 } },
2 => .{ .bytes = .{ in[0], in[1], 0, 0, 0, 0, 0, 0 } },
3 => .{ .bytes = .{ in[0], in[1], in[2], 0, 0, 0, 0, 0 } },
4 => .{ .bytes = .{ in[0], in[1], in[2], in[3], 0, 0, 0, 0 } },
5 => .{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], 0, 0, 0 } },
6 => .{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], 0, 0 } },
7 => .{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], in[6], 0 } },
max_inline_len =>
// If they use the final bit, then it's a big string.
// This should only happen for non-ascii strings that are exactly 8 bytes.
// so that's an edge-case
if ((in[max_inline_len - 1]) >= 128)
try initAppend(allocator, buf, in)
else
.{ .bytes = .{ in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7] } },
else => try initAppend(allocator, buf, in),
};
}
pub fn initAppend(
allocator: std.mem.Allocator,
buf: *std.ArrayListUnmanaged(u8),
in: string,
) OOM!String {
try buf.appendSlice(allocator, in);
const in_buf = buf.items[buf.items.len - in.len ..];
return @bitCast((@as(u64, 0) | @as(u64, @as(max_addressable_space, @truncate(@as(u64, @bitCast(Pointer.init(buf.items, in_buf))))))) | 1 << 63);
}
pub fn eql(this: String, that: String, this_buf: []const u8, that_buf: []const u8) bool {
if (this.isInline() and that.isInline()) {
return @as(u64, @bitCast(this.bytes)) == @as(u64, @bitCast(that.bytes));
} else if (this.isInline() != that.isInline()) {
return false;
} else {
const a = this.ptr();
const b = that.ptr();
return strings.eql(this_buf[a.off..][0..a.len], that_buf[b.off..][0..b.len]);
}
}
pub inline fn isEmpty(this: String) bool {
return @as(u64, @bitCast(this.bytes)) == @as(u64, 0);
}
pub fn len(this: String) usize {
switch (this.bytes[max_inline_len - 1] & 128) {
0 => {
// Edgecase: string that starts with a 0 byte will be considered empty.
switch (this.bytes[0]) {
0 => {
return 0;
},
else => {
comptime var i: usize = 0;
inline while (i < this.bytes.len) : (i += 1) {
if (this.bytes[i] == 0) return i;
}
return 8;
},
}
},
else => {
const ptr_ = this.ptr();
return ptr_.len;
},
}
}
pub const Pointer = extern struct {
off: u32 = 0,
len: u32 = 0,
pub inline fn init(
buf: string,
in: string,
) Pointer {
if (Environment.allow_assert) {
assert(bun.isSliceInBuffer(in, buf));
}
return Pointer{
.off = @as(u32, @truncate(@intFromPtr(in.ptr) - @intFromPtr(buf.ptr))),
.len = @as(u32, @truncate(in.len)),
};
}
};
pub inline fn ptr(this: String) Pointer {
return @as(Pointer, @bitCast(@as(u64, @as(u63, @truncate(@as(u64, @bitCast(this)))))));
}
pub fn toJS(this: *const String, buffer: []const u8, globalThis: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
return bun.String.createUTF8ForJS(globalThis, this.slice(buffer));
}
// String must be a pointer because we reference it as a slice. It will become a dead pointer if it is copied.
pub fn slice(this: *const String, buf: string) string {
switch (this.bytes[max_inline_len - 1] & 128) {
0 => {
// Edgecase: string that starts with a 0 byte will be considered empty.
switch (this.bytes[0]) {
0 => {
return "";
},
else => {
comptime var i: usize = 0;
inline while (i < this.bytes.len) : (i += 1) {
if (this.bytes[i] == 0) return this.bytes[0..i];
}
return &this.bytes;
},
}
},
else => {
const ptr_ = this.*.ptr();
return buf[ptr_.off..][0..ptr_.len];
},
}
}
pub const Builder = struct {
len: usize = 0,
cap: usize = 0,
ptr: ?[*]u8 = null,
string_pool: StringPool = undefined,
pub const StringPool = std.HashMap(u64, String, IdentityContext(u64), 80);
pub inline fn stringHash(buf: []const u8) u64 {
return bun.Wyhash11.hash(0, buf);
}
pub inline fn count(this: *Builder, slice_: string) void {
return countWithHash(this, slice_, if (slice_.len >= String.max_inline_len) stringHash(slice_) else std.math.maxInt(u64));
}
pub inline fn countWithHash(this: *Builder, slice_: string, hash: u64) void {
if (slice_.len <= String.max_inline_len) return;
if (!this.string_pool.contains(hash)) {
this.cap += slice_.len;
}
}
pub inline fn allocatedSlice(this: *Builder) []u8 {
return if (this.cap > 0)
this.ptr.?[0..this.cap]
else
&[_]u8{};
}
pub fn allocate(this: *Builder, allocator: Allocator) !void {
const ptr_ = try allocator.alloc(u8, this.cap);
this.ptr = ptr_.ptr;
}
pub fn append(this: *Builder, comptime Type: type, slice_: string) Type {
return @call(bun.callmod_inline, appendWithHash, .{ this, Type, slice_, stringHash(slice_) });
}
pub fn appendUTF8WithoutPool(this: *Builder, comptime Type: type, slice_: string, hash: u64) Type {
if (slice_.len <= String.max_inline_len) {
if (strings.isAllASCII(slice_)) {
switch (Type) {
String => {
return String.init(this.allocatedSlice(), slice_);
},
ExternalString => {
return ExternalString.init(this.allocatedSlice(), slice_, hash);
},
else => @compileError("Invalid type passed to StringBuilder"),
}
}
}
if (comptime Environment.allow_assert) {
assert(this.len <= this.cap); // didn't count everything
assert(this.ptr != null); // must call allocate first
}
bun.copy(u8, this.ptr.?[this.len..this.cap], slice_);
const final_slice = this.ptr.?[this.len..this.cap][0..slice_.len];
this.len += slice_.len;
if (comptime Environment.allow_assert) assert(this.len <= this.cap);
switch (Type) {
String => {
return String.init(this.allocatedSlice(), final_slice);
},
ExternalString => {
return ExternalString.init(this.allocatedSlice(), final_slice, hash);
},
else => @compileError("Invalid type passed to StringBuilder"),
}
}
// SlicedString is not supported due to inline strings.
pub fn appendWithoutPool(this: *Builder, comptime Type: type, slice_: string, hash: u64) Type {
if (slice_.len <= String.max_inline_len) {
switch (Type) {
String => {
return String.init(this.allocatedSlice(), slice_);
},
ExternalString => {
return ExternalString.init(this.allocatedSlice(), slice_, hash);
},
else => @compileError("Invalid type passed to StringBuilder"),
}
}
if (comptime Environment.allow_assert) {
assert(this.len <= this.cap); // didn't count everything
assert(this.ptr != null); // must call allocate first
}
bun.copy(u8, this.ptr.?[this.len..this.cap], slice_);
const final_slice = this.ptr.?[this.len..this.cap][0..slice_.len];
this.len += slice_.len;
if (comptime Environment.allow_assert) assert(this.len <= this.cap);
switch (Type) {
String => {
return String.init(this.allocatedSlice(), final_slice);
},
ExternalString => {
return ExternalString.init(this.allocatedSlice(), final_slice, hash);
},
else => @compileError("Invalid type passed to StringBuilder"),
}
}
pub fn appendWithHash(this: *Builder, comptime Type: type, slice_: string, hash: u64) Type {
if (slice_.len <= String.max_inline_len) {
switch (Type) {
String => {
return String.init(this.allocatedSlice(), slice_);
},
ExternalString => {
return ExternalString.init(this.allocatedSlice(), slice_, hash);
},
else => @compileError("Invalid type passed to StringBuilder"),
}
}
if (comptime Environment.allow_assert) {
assert(this.len <= this.cap); // didn't count everything
assert(this.ptr != null); // must call allocate first
}
const string_entry = this.string_pool.getOrPut(hash) catch unreachable;
if (!string_entry.found_existing) {
bun.copy(u8, this.ptr.?[this.len..this.cap], slice_);
const final_slice = this.ptr.?[this.len..this.cap][0..slice_.len];
this.len += slice_.len;
string_entry.value_ptr.* = String.init(this.allocatedSlice(), final_slice);
}
if (comptime Environment.allow_assert) assert(this.len <= this.cap);
switch (Type) {
String => {
return string_entry.value_ptr.*;
},
ExternalString => {
return ExternalString{
.value = string_entry.value_ptr.*,
.hash = hash,
};
},
else => @compileError("Invalid type passed to StringBuilder"),
}
}
};
comptime {
if (@sizeOf(String) != @sizeOf(Pointer)) {
@compileError("String types must be the same size");
}
}
};
const string = []const u8;
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("bun");
const Environment = bun.Environment;
const IdentityContext = bun.IdentityContext;
const OOM = bun.OOM;
const assert = bun.assert;
const jsc = bun.jsc;
const strings = bun.strings;
const Lockfile = bun.install.Lockfile;
const ExternalString = bun.Semver.ExternalString;
const SlicedString = bun.Semver.SlicedString;