Files
bun.sh/src/meta.zig
taylor.fish 712d5be741 Add safety checks to MultiArrayList and BabyList (#21063)
Ensure we aren't using multiple allocators with the same list by storing
a pointer to the allocator in debug mode only.

This check is stricter than the bare minimum necessary to prevent
illegal behavior, so CI may reveal certain uses that fail the checks but
don't cause IB. Most of these cases should probably be updated to comply
with the new requirements—we want these types' invariants to be clear.

(For internal tracking: fixes ENG-14987)
2025-07-25 18:12:21 -07:00

362 lines
11 KiB
Zig

pub fn OptionalChild(comptime T: type) type {
const tyinfo = @typeInfo(T);
if (tyinfo != .pointer) @compileError("OptionalChild(T) requires that T be a pointer to an optional type.");
const child = @typeInfo(tyinfo.pointer.child);
if (child != .Optional) @compileError("OptionalChild(T) requires that T be a pointer to an optional type.");
return child.Optional.child;
}
pub fn EnumFields(comptime T: type) []const std.builtin.Type.EnumField {
const tyinfo = @typeInfo(T);
return switch (tyinfo) {
.@"union" => std.meta.fields(tyinfo.@"union".tag_type.?),
.@"enum" => tyinfo.@"enum".fields,
else => {
@compileError("Used `EnumFields(T)` on a type that is not an `enum` or a `union(enum)`");
},
};
}
pub fn ReturnOfMaybe(comptime function: anytype) type {
const Func = @TypeOf(function);
const typeinfo: std.builtin.Type.Fn = @typeInfo(Func).@"fn";
const MaybeType = typeinfo.return_type orelse @compileError("Expected the function to have a return type");
return MaybeResult(MaybeType);
}
pub fn MaybeResult(comptime MaybeType: type) type {
const maybe_ty_info = @typeInfo(MaybeType);
const maybe = maybe_ty_info.@"union";
if (maybe.fields.len != 2) @compileError("Expected the Maybe type to be a union(enum) with two variants");
if (!std.mem.eql(u8, maybe.fields[0].name, "err")) {
@compileError("Expected the first field of the Maybe type to be \"err\", got: " ++ maybe.fields[0].name);
}
if (!std.mem.eql(u8, maybe.fields[1].name, "result")) {
@compileError("Expected the second field of the Maybe type to be \"result\"" ++ maybe.fields[1].name);
}
return maybe.fields[1].type;
}
pub fn ReturnOf(comptime function: anytype) type {
return ReturnOfType(@TypeOf(function));
}
pub fn ReturnOfType(comptime Type: type) type {
const typeinfo: std.builtin.Type.Fn = @typeInfo(Type).@"fn";
return typeinfo.return_type orelse void;
}
pub fn typeName(comptime Type: type) []const u8 {
const name = @typeName(Type);
return typeBaseName(name);
}
/// partially emulates behaviour of @typeName in previous Zig versions,
/// converting "some.namespace.MyType" to "MyType"
pub inline fn typeBaseName(comptime fullname: [:0]const u8) [:0]const u8 {
@setEvalBranchQuota(1_000_000);
// leave type name like "namespace.WrapperType(namespace.MyType)" as it is
const baseidx = comptime std.mem.indexOf(u8, fullname, "(");
if (baseidx != null) return comptime fullname;
const idx = comptime std.mem.lastIndexOf(u8, fullname, ".");
const name = if (idx == null) fullname else fullname[(idx.? + 1)..];
return comptime name;
}
pub fn enumFieldNames(comptime Type: type) []const []const u8 {
var names: [std.meta.fields(Type).len][]const u8 = std.meta.fieldNames(Type).*;
var i: usize = 0;
for (names) |name| {
// zig seems to include "_" or an empty string in the list of enum field names
// it makes sense, but humans don't want that
if (bun.strings.eqlAnyComptime(name, &.{ "_none", "", "_" })) {
continue;
}
names[i] = name;
i += 1;
}
return names[0..i];
}
pub fn banFieldType(comptime Container: type, comptime T: type) void {
comptime {
for (std.meta.fields(Container)) |field| {
if (field.type == T) {
@compileError(std.fmt.comptimePrint(typeName(T) ++ " field \"" ++ field.name ++ "\" not allowed in " ++ typeName(Container), .{}));
}
}
}
}
// []T -> T
// *const T -> T
// *[n]T -> T
pub fn Item(comptime T: type) type {
switch (@typeInfo(T)) {
.pointer => |ptr| {
if (ptr.size == .one) {
switch (@typeInfo(ptr.child)) {
.array => |array| {
return array.child;
},
else => {},
}
}
return ptr.child;
},
else => return std.meta.Child(T),
}
}
/// Returns .{a, ...args_}
pub fn ConcatArgs1(
comptime func: anytype,
a: anytype,
args_: anytype,
) std.meta.ArgsTuple(@TypeOf(func)) {
var args: std.meta.ArgsTuple(@TypeOf(func)) = undefined;
args[0] = a;
inline for (args_, 1..) |arg, i| {
args[i] = arg;
}
return args;
}
/// Returns .{a, b, ...args_}
pub inline fn ConcatArgs2(
comptime func: anytype,
a: anytype,
b: anytype,
args_: anytype,
) std.meta.ArgsTuple(@TypeOf(func)) {
var args: std.meta.ArgsTuple(@TypeOf(func)) = undefined;
args[0] = a;
args[1] = b;
inline for (args_, 2..) |arg, i| {
args[i] = arg;
}
return args;
}
/// Returns .{a, b, c, d, ...args_}
pub inline fn ConcatArgs4(
comptime func: anytype,
a: anytype,
b: anytype,
c: anytype,
d: anytype,
args_: anytype,
) std.meta.ArgsTuple(@TypeOf(func)) {
var args: std.meta.ArgsTuple(@TypeOf(func)) = undefined;
args[0] = a;
args[1] = b;
args[2] = c;
args[3] = d;
inline for (args_, 4..) |arg, i| {
args[i] = arg;
}
return args;
}
// Copied from std.meta
fn CreateUniqueTuple(comptime N: comptime_int, comptime types: [N]type) type {
var tuple_fields: [types.len]std.builtin.Type.StructField = undefined;
inline for (types, 0..) |T, i| {
@setEvalBranchQuota(10_000);
var num_buf: [128]u8 = undefined;
tuple_fields[i] = .{
.name = std.fmt.bufPrintZ(&num_buf, "{d}", .{i}) catch unreachable,
.type = T,
.default_value_ptr = null,
.is_comptime = false,
.alignment = if (@sizeOf(T) > 0) @alignOf(T) else 0,
};
}
return @Type(.{
.@"struct" = .{
.is_tuple = true,
.layout = .auto,
.decls = &.{},
.fields = &tuple_fields,
},
});
}
pub fn hasStableMemoryLayout(comptime T: type) bool {
const tyinfo = @typeInfo(T);
return switch (tyinfo) {
.Type => true,
.Void => true,
.Bool => true,
.Int => true,
.Float => true,
.@"enum" => {
// not supporting this rn
if (tyinfo.@"enum".is_exhaustive) return false;
return hasStableMemoryLayout(tyinfo.@"enum".tag_type);
},
.@"struct" => switch (tyinfo.@"struct".layout) {
.auto => {
inline for (tyinfo.@"struct".fields) |field| {
if (!hasStableMemoryLayout(field.field_type)) return false;
}
return true;
},
.@"extern" => true,
.@"packed" => false,
},
.@"union" => switch (tyinfo.@"union".layout) {
.auto => {
if (tyinfo.@"union".tag_type == null or !hasStableMemoryLayout(tyinfo.@"union".tag_type.?)) return false;
inline for (tyinfo.@"union".fields) |field| {
if (!hasStableMemoryLayout(field.type)) return false;
}
return true;
},
.@"extern" => true,
.@"packed" => false,
},
else => true,
};
}
pub fn isSimpleCopyType(comptime T: type) bool {
@setEvalBranchQuota(1_000_000);
const tyinfo = @typeInfo(T);
return switch (tyinfo) {
.void => true,
.bool => true,
.int => true,
.float => true,
.@"enum" => true,
.@"struct" => {
inline for (tyinfo.@"struct".fields) |field| {
if (!isSimpleCopyType(field.type)) return false;
}
return true;
},
.@"union" => {
inline for (tyinfo.@"union".fields) |field| {
if (!isSimpleCopyType(field.type)) return false;
}
return true;
},
.optional => return isSimpleCopyType(tyinfo.optional.child),
else => false,
};
}
pub fn isScalar(comptime T: type) bool {
return switch (T) {
i32, u32, i64, u64, f32, f64, bool => true,
else => {
const tyinfo = @typeInfo(T);
if (tyinfo == .@"enum") return true;
return false;
},
};
}
pub fn isSimpleEqlType(comptime T: type) bool {
const tyinfo = @typeInfo(T);
return switch (tyinfo) {
.type => true,
.void => true,
.bool => true,
.int => true,
.float => true,
.@"enum" => true,
.@"struct" => |struct_info| struct_info.layout == .@"packed",
else => false,
};
}
pub const ListContainerType = enum {
array_list,
baby_list,
small_list,
};
pub fn looksLikeListContainerType(comptime T: type) ?struct { list: ListContainerType, child: type } {
const tyinfo = @typeInfo(T);
if (tyinfo == .@"struct") {
// Looks like array list
if (tyinfo.@"struct".fields.len == 2 and
std.mem.eql(u8, tyinfo.@"struct".fields[0].name, "items") and
std.mem.eql(u8, tyinfo.@"struct".fields[1].name, "capacity"))
return .{ .list = .array_list, .child = std.meta.Child(tyinfo.@"struct".fields[0].type) };
// Looks like babylist
if (tyinfo.@"struct".fields.len == 4 and
std.mem.eql(u8, tyinfo.@"struct".fields[0].name, "ptr") and
std.mem.eql(u8, tyinfo.@"struct".fields[1].name, "len") and
std.mem.eql(u8, tyinfo.@"struct".fields[2].name, "cap") and
std.mem.eql(u8, tyinfo.@"struct".fields[3].name, "alloc_ptr"))
return .{ .list = .baby_list, .child = std.meta.Child(tyinfo.@"struct".fields[0].type) };
// Looks like SmallList
if (tyinfo.@"struct".fields.len == 2 and
std.mem.eql(u8, tyinfo.@"struct".fields[0].name, "capacity") and
std.mem.eql(u8, tyinfo.@"struct".fields[1].name, "data")) return .{
.list = .small_list,
.child = std.meta.Child(
@typeInfo(tyinfo.@"struct".fields[1].type).@"union".fields[0].type,
),
};
}
return null;
}
pub fn Tagged(comptime U: type, comptime T: type) type {
var info: std.builtin.Type.Union = @typeInfo(U).@"union";
info.tag_type = T;
info.decls = &.{};
return @Type(.{ .@"union" = info });
}
pub fn SliceChild(comptime T: type) type {
const tyinfo = @typeInfo(T);
if (tyinfo == .pointer and tyinfo.pointer.size == .slice) {
return tyinfo.pointer.child;
}
return T;
}
/// userland implementation of https://github.com/ziglang/zig/issues/21879
pub fn VoidFieldTypes(comptime T: type) type {
const fields = @typeInfo(T).@"struct".fields;
var new_fields = fields[0..fields.len].*;
for (&new_fields) |*field| {
field.type = void;
field.default_value_ptr = null;
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &new_fields,
.decls = &.{},
.is_tuple = false,
} });
}
pub fn voidFieldTypeDiscardHelper(data: anytype) void {
_ = data;
}
const bun = @import("bun");
const std = @import("std");