From 16e93505d48a40b3af88b2707f45c453f545de9f Mon Sep 17 00:00:00 2001 From: ItsMeSamey Date: Sun, 4 Oct 2026 08:40:59 +1000 Subject: [PATCH] build: migrate to Zig 0.17.0 --- .github/workflows/ci.yml | 2 +- CONTRIBUTING.md | 4 ++- DOCUMENTATION.md | 2 +- README.md | 2 +- build.zig | 21 ++++++++------ build.zig.zon | 2 +- src/html/attr.zig | 6 ++-- src/html/document.zig | 23 ++++++++-------- src/html/entities.zig | 2 +- src/html/parser.zig | 6 ++-- src/html/scanner.zig | 4 +-- src/html/stream.zig | 4 +-- src/html/tables.zig | 4 +-- src/selector/execution_plan.zig | 6 ++-- src/selector/forward.zig | 6 ++-- src/selector/matcher.zig | 10 +++---- src/selector/predicate_plan.zig | 12 ++++---- src/testing.zig | 39 +++++++++++++------------- tools/bench/bench.zig | 2 +- tools/scripts.zig | 26 ++++++++++-------- tools/test_runner.zig | 47 ++++++++++++++++---------------- tools/tests/public_api_tests.zig | 6 ++-- 22 files changed, 123 insertions(+), 113 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35c4a6a..04db808 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Setup Zig uses: mlugg/setup-zig@v2 with: - version: 0.16.0 + version: 0.17.0 - name: Zig fmt check shell: bash diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6356337..1948d47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Prerequisites -- Zig `0.16.0` +- Zig `0.17.0` - Git ## Local Development Workflow @@ -21,6 +21,8 @@ zig build bench-compare zig build conformance ``` +Use `zig build test -j1 -Dtest-jobs=1` to bound both compilation and test-process concurrency. + ## Style - Format Zig code before pushing: diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index c0df2e3..1bc62d1 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -18,7 +18,7 @@ This is the canonical manual for usage, API, selector behavior, performance work ## Requirements -- Zig `0.16.0` +- Zig `0.17.0` - Mutable input buffers (`[]u8`) for destructive parsing - `[]const u8` inputs are supported when `ParseOptions.non_destructive = true` diff --git a/README.md b/README.md index 44635c0..8fbc387 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ High-throughput HTML parser + CSS selector engine for Zig. -[![zig](https://img.shields.io/badge/zig-0.16.0-orange)](https://ziglang.org/) +[![zig](https://img.shields.io/badge/zig-0.17.0-orange)](https://ziglang.org/) [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE) ## ⚠️ Conformance Warning diff --git a/build.zig b/build.zig index b36fe13..8414e3a 100644 --- a/build.zig +++ b/build.zig @@ -11,6 +11,7 @@ const IntLen = enum { pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const test_jobs = b.option(usize, "test-jobs", "Maximum concurrent test processes"); const intlen = b.option(IntLen, "intlen", "Integer width used for document spans and node indexes") orelse .u32; const config_options = b.addOptions(); @@ -113,20 +114,19 @@ pub fn build(b: *std.Build) void { docs_check_cmd.step.dependOn(b.getInstallStep()); examples_check_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - tools_cmd.addArgs(args); - compare_cmd.addArgs(args); - interleaved_cmd.addArgs(args); - conformance_cmd.addArgs(args); - docs_check_cmd.addArgs(args); - examples_check_cmd.addArgs(args); - } + tools_cmd.addPassthruArgs(); + compare_cmd.addPassthruArgs(); + interleaved_cmd.addPassthruArgs(); + conformance_cmd.addPassthruArgs(); + docs_check_cmd.addPassthruArgs(); + examples_check_cmd.addPassthruArgs(); const mod_tests = b.addTest(.{ .root_module = mod, .test_runner = .{ .path = b.path("tools/test_runner.zig"), .mode = .simple }, }); const run_mod_tests = b.addRunArtifact(mod_tests); + if (test_jobs) |jobs| run_mod_tests.addArgs(&.{ "--jobs", b.fmt("{d}", .{jobs}) }); const examples_tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -148,6 +148,7 @@ pub fn build(b: *std.Build) void { .test_runner = .{ .path = b.path("tools/test_runner.zig"), .mode = .simple }, }); const run_examples_tests = b.addRunArtifact(examples_tests); + if (test_jobs) |jobs| run_examples_tests.addArgs(&.{ "--jobs", b.fmt("{d}", .{jobs}) }); const behavioral_tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -161,6 +162,7 @@ pub fn build(b: *std.Build) void { .test_runner = .{ .path = b.path("tools/test_runner.zig"), .mode = .simple }, }); const run_behavioral_tests = b.addRunArtifact(behavioral_tests); + if (test_jobs) |jobs| run_behavioral_tests.addArgs(&.{ "--jobs", b.fmt("{d}", .{jobs}) }); const public_api_tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -174,6 +176,7 @@ pub fn build(b: *std.Build) void { .test_runner = .{ .path = b.path("tools/test_runner.zig"), .mode = .simple }, }); const run_public_api_tests = b.addRunArtifact(public_api_tests); + if (test_jobs) |jobs| run_public_api_tests.addArgs(&.{ "--jobs", b.fmt("{d}", .{jobs}) }); const public_api_test_step = b.step("test-public-api", "Run exhaustive consumer-facing public API tests"); public_api_test_step.dependOn(&run_public_api_tests.step); @@ -186,6 +189,7 @@ pub fn build(b: *std.Build) void { .test_runner = .{ .path = b.path("tools/test_runner.zig"), .mode = .simple }, }); const run_scripts_tests = b.addRunArtifact(scripts_tests); + if (test_jobs) |jobs| run_scripts_tests.addArgs(&.{ "--jobs", b.fmt("{d}", .{jobs}) }); const bench_tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -200,6 +204,7 @@ pub fn build(b: *std.Build) void { .test_runner = .{ .path = b.path("tools/test_runner.zig"), .mode = .simple }, }); const run_bench_tests = b.addRunArtifact(bench_tests); + if (test_jobs) |jobs| run_bench_tests.addArgs(&.{ "--jobs", b.fmt("{d}", .{jobs}) }); const test_step = b.step("test", "Run tests"); test_step.dependOn(&run_mod_tests.step); diff --git a/build.zig.zon b/build.zig.zon index 6ba6b9c..924c141 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = .html, .version = "0.1.0", .fingerprint = 0x1879f8e53a69d776, - .minimum_zig_version = "0.16.0", + .minimum_zig_version = "0.17.0", .dependencies = .{}, .paths = .{ "build.zig", diff --git a/src/html/attr.zig b/src/html/attr.zig index 2cf2bb6..ef82504 100644 --- a/src/html/attr.zig +++ b/src/html/attr.zig @@ -232,7 +232,7 @@ pub const CompactIterator = struct { self.cursor += 1; return .{ .name = name, .value = null }; } - const marker: CompactValueMarker = @enumFromInt(self.source[self.cursor]); + const marker: CompactValueMarker = @fromBackingInt(@intCast(self.source[self.cursor])); self.cursor += 1; const value_start = self.cursor; while (self.cursor < self.source.len and self.source[self.cursor] != 0) : (self.cursor += 1) {} @@ -308,12 +308,12 @@ pub fn materializeAttributes(comptime entity_decoding: entities.EntityDecoding, const raw_slice = source[raw.start..raw.end]; const decoded = entities.decodeAttributeInPlaceResultWithMode(entity_decoding, raw_slice, null); if (decoded.complete) { - source[write] = @intFromEnum(CompactValueMarker.decoded); + source[write] = @backingInt(CompactValueMarker.decoded); write += 1; std.mem.copyForwards(u8, source[write .. write + decoded.len], source[raw.start .. raw.start + decoded.len]); write += decoded.len; } else { - source[write] = @intFromEnum(rawCompactMarker(raw)); + source[write] = @backingInt(rawCompactMarker(raw)); write += 1; std.mem.copyForwards(u8, source[write .. write + raw_slice.len], raw_slice); for (source[write .. write + raw_slice.len]) |*b| { diff --git a/src/html/document.zig b/src/html/document.zig index a5153ec..4b410e2 100644 --- a/src/html/document.zig +++ b/src/html/document.zig @@ -485,9 +485,9 @@ fn GetNode(comptime options: ParseOptions) type { const end: usize = node.name_or_text.end(); if (end >= doc.source.len) return .raw; return switch (doc.source[end]) { - @intFromEnum(RwTextState.decoded) => .decoded, - @intFromEnum(RwTextState.decode_failed) => .decode_failed, - @intFromEnum(RwTextState.raw_normalized) => .raw_normalized, + @backingInt(RwTextState.decoded) => .decoded, + @backingInt(RwTextState.decode_failed) => .decode_failed, + @backingInt(RwTextState.raw_normalized) => .raw_normalized, else => .raw, }; } @@ -496,7 +496,7 @@ fn GetNode(comptime options: ParseOptions) type { if (comptime options.non_destructive) return; if (state == .raw) return; const end: usize = node.name_or_text.end(); - if (end < doc.source.len) doc.source[end] = @intFromEnum(state); + if (end < doc.source.len) doc.source[end] = @backingInt(state); } /// Opaque children preserve both markup-looking bytes and entity syntax. @@ -2124,7 +2124,7 @@ test "innerTextOwned returns allocated output and materializes RW source text" { const text_node_after = doc.nodes[node.index + 1]; try std.testing.expectEqualStrings("a & b", text_node_after.name_or_text.slice(doc.source)); - try std.testing.expectEqual(@as(u8, @intFromEnum(RwTextState.decoded)), doc.source[text_node_after.name_or_text.end()]); + try std.testing.expectEqual(@as(u8, @backingInt(RwTextState.decoded)), doc.source[text_node_after.name_or_text.end()]); } test "RW text decoding stores and moves the trailing decoded marker" { @@ -2140,13 +2140,13 @@ test "RW text decoding stores and moves the trailing decoded marker" { const decoded = try p.innerTextWithOptions(alloc, .{ .normalize_whitespace = false }); try std.testing.expectEqualStrings("a& b ", decoded.value); const decoded_end: usize = doc.nodes[text_idx].name_or_text.end(); - try std.testing.expectEqual(@as(u8, @intFromEnum(RwTextState.decoded)), doc.source[decoded_end]); + try std.testing.expectEqual(@as(u8, @backingInt(RwTextState.decoded)), doc.source[decoded_end]); const normalized = try p.innerTextWithOptions(alloc, .{ .unescape = false, .normalize_whitespace = true }); try std.testing.expectEqualStrings("a& b", normalized.value); const normalized_end: usize = doc.nodes[text_idx].name_or_text.end(); try std.testing.expect(normalized_end < decoded_end); - try std.testing.expectEqual(@as(u8, @intFromEnum(RwTextState.decoded)), doc.source[normalized_end]); + try std.testing.expectEqual(@as(u8, @backingInt(RwTextState.decoded)), doc.source[normalized_end]); } test "RW terminal text marks only when decoding creates room" { @@ -2161,7 +2161,7 @@ test "RW terminal text marks only when decoding creates room" { try std.testing.expectEqualStrings("x&y", decoded.value); const encoded_text = encoded_doc.nodes[encoded_p.index + 1]; try std.testing.expect(encoded_text.name_or_text.end() < encoded_doc.source.len); - try std.testing.expectEqual(@as(u8, @intFromEnum(RwTextState.decoded)), encoded_doc.source[encoded_text.name_or_text.end()]); + try std.testing.expectEqual(@as(u8, @backingInt(RwTextState.decoded)), encoded_doc.source[encoded_text.name_or_text.end()]); var plain_doc = GetDocument(.{}).init(alloc); defer plain_doc.deinit(); @@ -2190,7 +2190,7 @@ test "RW multi-node owned text materializes each span and still normalizes after if (!doc.nodes[idx].isText(idx)) continue; const end: usize = doc.nodes[idx].name_or_text.end(); try std.testing.expect(end < doc.source.len); - try std.testing.expectEqual(@as(u8, @intFromEnum(RwTextState.decoded)), doc.source[end]); + try std.testing.expectEqual(@as(u8, @backingInt(RwTextState.decoded)), doc.source[end]); try std.testing.expect(std.mem.indexOf(u8, doc.nodes[idx].name_or_text.slice(doc.source), "&") == null); } } @@ -2386,7 +2386,7 @@ test "expanding full entities use marked allocating fallbacks" { const text_idx = div.index + 1; const text_end: usize = doc.nodes[text_idx].name_or_text.end(); try std.testing.expect(text_end < doc.source.len); - try std.testing.expectEqual(@as(u8, @intFromEnum(RwTextState.decode_failed)), doc.source[text_end]); + try std.testing.expectEqual(@as(u8, @backingInt(RwTextState.decode_failed)), doc.source[text_end]); var never_out: std.Io.Writer.Allocating = .init(alloc); defer never_out.deinit(); @@ -4612,7 +4612,8 @@ test "query collect frees partial output when growth fails" { defer alloc.free(doc_source); try resetParsed(.{}, &doc, doc_source); - var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 1 }); + // Reject in-place growth too, so SafeAllocator cannot bypass the failing allocation. + var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 1, .resize_fail_index = 0 }); var it = doc.query("span"); try std.testing.expectError(error.OutOfMemory, it.collect(failing.allocator())); } diff --git a/src/html/entities.zig b/src/html/entities.zig index 89e8b2d..6c11539 100644 --- a/src/html/entities.zig +++ b/src/html/entities.zig @@ -415,7 +415,7 @@ fn replacementDecoded(consumed: usize) Decoded { } const NumericDigitTable = blk: { - var table = [_]u8{InvalidDigit} ** 256; + var table: [256]u8 = @splat(InvalidDigit); var c: u8 = '0'; while (c <= '9') : (c += 1) table[c] = c - '0'; c = 'a'; diff --git a/src/html/parser.zig b/src/html/parser.zig index 5efed2e..ea86d09 100644 --- a/src/html/parser.zig +++ b/src/html/parser.zig @@ -67,7 +67,7 @@ fn ParseState(comptime opts: ParseOptions) type { parse_stack_heap_owned: bool = false, /// Optional-end-tag source classes currently present on the open stack. implicit_source_mask: u8 = 0, - implicit_source_counts: [8]IndexInt = .{0} ** 8, + implicit_source_counts: [8]IndexInt = @splat(0), /// Malformed-close index exists only after a deep full-stack miss. tag_index: ?*open_tag_index.LiveIndex(OpenElem) = null, @@ -735,8 +735,8 @@ fn ParseState(comptime opts: ParseOptions) type { inline fn addNode(noalias self: *Self, name_or_text: anytype, is_element: bool, overrides: anytype) !void { const Overrides = @TypeOf(overrides); - comptime for (@typeInfo(Overrides).@"struct".fields) |field| { - std.debug.assert(std.mem.eql(u8, field.name, "parent")); + comptime for (@typeInfo(Overrides).@"struct".field_names) |field_name| { + std.debug.assert(std.mem.eql(u8, field_name, "parent")); }; const parent_idx: IndexInt = @intCast(if (@hasField(Overrides, "parent")) overrides.parent else self.currentParent()); const idx: IndexInt = @intCast(self.nodes.items.len); diff --git a/src/html/scanner.zig b/src/html/scanner.zig index 55fce44..aae087a 100644 --- a/src/html/scanner.zig +++ b/src/html/scanner.zig @@ -28,7 +28,7 @@ pub noinline fn countByte(source: []const u8, value: u8) usize { if (std.simd.suggestVectorLength(u8)) |suggested_len| { const Block = @Vector(suggested_len, u8); const BoolBlock = @Vector(suggested_len, bool); - const MaskInt = std.meta.Int(.unsigned, suggested_len); + const MaskInt = @Int(.unsigned, suggested_len); const mask: Block = @splat(value); while (index + suggested_len <= source.len) : (index += suggested_len) { @@ -65,7 +65,7 @@ pub fn findBytePosOrEnd(source: []const u8, start: usize, value: u8) usize { const block_cnt = @max(1, block_len_sum / block_len); const Block = @Vector(block_len, u8); const BoolBlock = @Vector(block_len, bool); - const MaskInt = std.meta.Int(.unsigned, block_len); + const MaskInt = @Int(.unsigned, block_len); const mask: Block = @splat(value); if (index + block_len_sum <= source.len) while (true) { diff --git a/src/html/stream.zig b/src/html/stream.zig index e6ca748..985c743 100644 --- a/src/html/stream.zig +++ b/src/html/stream.zig @@ -189,7 +189,7 @@ fn State(comptime Ctx: type, comptime callback: anytype, comptime static_options stack_heap: bool = false, tag_index: open_tag_index.LiveIndex(OpenTag) = .{}, implicit_source_mask: u8 = 0, - implicit_source_counts: [8]IndexInt = .{0} ** 8, + implicit_source_counts: [8]IndexInt = @splat(0), slow_close_misses: u8 = 0, const Self = @This(); @@ -207,7 +207,7 @@ fn State(comptime Ctx: type, comptime callback: anytype, comptime static_options }; const blocker_by_boundary = blk: { const B = tags.ImplicitCloseBoundaryMask; - var table = [_]u8{0} ** 32; + var table: [32]u8 = @splat(0); table[B.regular] = ImplicitBlockers.regular; table[B.button] = ImplicitBlockers.button; table[B.list_item] = ImplicitBlockers.list_item; diff --git a/src/html/tables.zig b/src/html/tables.zig index ea8c11d..2b39af3 100644 --- a/src/html/tables.zig +++ b/src/html/tables.zig @@ -8,7 +8,7 @@ test { /// Builds a 256-entry boolean lookup table from a predicate. pub fn makeClassTable(comptime predicate: fn (u8) bool) [256]bool { @setEvalBranchQuota(10_000); - var table = [_]bool{false} ** 256; + var table: [256]bool = @splat(false); inline for (0..256) |i| { table[i] = predicate(@as(u8, @intCast(i))); } @@ -46,7 +46,7 @@ pub const TagNameCharTable = makeClassTable(isTagNameChar); /// Canonical lowercase byte for tag-name bytes; zero marks a tokenizer terminator. pub const TagNameLowerTable = blk: { @setEvalBranchQuota(10_000); - var table = [_]u8{0} ** 256; + var table: [256]u8 = @splat(0); for (0..256) |i| { const c: u8 = @intCast(i); if (isTagNameChar(c)) table[i] = std.ascii.toLower(c); diff --git a/src/selector/execution_plan.zig b/src/selector/execution_plan.zig index 231275c..d64eab2 100644 --- a/src/selector/execution_plan.zig +++ b/src/selector/execution_plan.zig @@ -25,7 +25,7 @@ pub const Mask = enum(usize) { scope_lineage, }; -pub const MaskCount = @typeInfo(Mask).@"enum".fields.len; +pub const MaskCount = @typeInfo(Mask).@"enum".field_names.len; pub const StatefulGroup = struct { compound_start: IndexInt, @@ -162,12 +162,12 @@ pub const Plan = struct { } pub fn mask(self: *const @This(), which: Mask) []const u64 { - const start = @intFromEnum(which) * self.word_count; + const start = @backingInt(which) * self.word_count; return self.masks[start .. start + self.word_count]; } pub fn maskMut(self: *@This(), which: Mask) []u64 { - const start = @intFromEnum(which) * self.word_count; + const start = @backingInt(which) * self.word_count; return self.masks[start .. start + self.word_count]; } diff --git a/src/selector/forward.zig b/src/selector/forward.zig index 02b133d..626eccb 100644 --- a/src/selector/forward.zig +++ b/src/selector/forward.zig @@ -153,7 +153,7 @@ pub fn Executor(comptime Doc: type) type { seed_workspace: ?*matcher.MatchWorkspace = null, stats: if (builtin.is_test) Stats else void = if (builtin.is_test) .{} else {}, initialized: bool = false, - tag_cache: [16]TagCacheEntry = [_]TagCacheEntry{.{}} ** 16, + tag_cache: [16]TagCacheEntry = @splat(.{}), const Self = @This(); const TagCacheEntry = struct { @@ -490,7 +490,7 @@ pub fn WideExecutor(comptime Doc: type) type { stack: std.ArrayListUnmanaged(WideFrame) = .empty, node_ctx: matcher.NodeContext = .{}, predicate_word_count: usize = 0, - small_seen_predicates: [SmallPredicateWordLimit]u64 = [_]u64{0} ** SmallPredicateWordLimit, + small_seen_predicates: [SmallPredicateWordLimit]u64 = @splat(0), seen_predicates: []u64 = &.{}, matched_predicates: []u64 = &.{}, state_fanned_predicates: []u64 = &.{}, @@ -1032,7 +1032,7 @@ pub fn WideExecutor(comptime Doc: type) type { } fn state(self: *Self, slot: usize, which: State) []u64 { - const start = (slot * 4 + @intFromEnum(which)) * self.word_count; + const start = (slot * 4 + @backingInt(which)) * self.word_count; return self.states.items[start .. start + self.word_count]; } diff --git a/src/selector/matcher.zig b/src/selector/matcher.zig index a16ad7c..fbb82dd 100644 --- a/src/selector/matcher.zig +++ b/src/selector/matcher.zig @@ -63,7 +63,7 @@ fn evalAttrOpIgnoreCase(raw: []const u8, value: []const u8, op: ast.AttrOp) bool .eq => std.ascii.eqlIgnoreCase(raw, value), .prefix => std.ascii.startsWithIgnoreCase(raw, value), .suffix => std.ascii.endsWithIgnoreCase(raw, value), - .contains => std.ascii.indexOfIgnoreCase(raw, value) != null, + .contains => std.ascii.findIgnoreCase(raw, value) != null, .includes => tokenIncludesIgnoreCaseAscii(raw, value), .dash_match => std.ascii.eqlIgnoreCase(raw, value) or (raw.len > value.len and std.ascii.startsWithIgnoreCase(raw, value) and raw[value.len] == '-'), }; @@ -179,7 +179,7 @@ const ReverseInlineMapCapacity = 256; const ReverseInlineMapMaxLoad = ReverseInlineMapCapacity * 7 / 10; const ReverseNodeMap = struct { - keys: [ReverseInlineMapCapacity]IndexInt = [_]IndexInt{InvalidIndex} ** ReverseInlineMapCapacity, + keys: [ReverseInlineMapCapacity]IndexInt = @splat(InvalidIndex), values: [ReverseInlineMapCapacity]IndexInt = undefined, count: usize = 0, spill: std.AutoHashMapUnmanaged(IndexInt, IndexInt) = .empty, @@ -257,7 +257,7 @@ const ReverseNodeMap = struct { const ReverseQueue = struct { const BucketCount = 65; - buckets: [BucketCount]std.ArrayListUnmanaged(IndexInt) = [_]std.ArrayListUnmanaged(IndexInt){.empty} ** BucketCount, + buckets: [BucketCount]std.ArrayListUnmanaged(IndexInt) = @splat(.empty), last_key: u64 = 0, len: usize = 0, @@ -974,8 +974,8 @@ const CollectedAttrs = struct { requested_once: bool = false, materialized: bool = false, names: [MaxCollectedAttrs][]const u8 = undefined, - values: [MaxCollectedAttrs]?[]const u8 = [_]?[]const u8{null} ** MaxCollectedAttrs, - looked: [MaxCollectedAttrs]bool = [_]bool{false} ** MaxCollectedAttrs, + values: [MaxCollectedAttrs]?[]const u8 = @splat(null), + looked: [MaxCollectedAttrs]bool = @splat(false), }; fn prepareCollectedAttrs(selector: ast.Selector, comp: ast.Compound, out: *CollectedAttrs) bool { diff --git a/src/selector/predicate_plan.zig b/src/selector/predicate_plan.zig index 5fb4186..2ec7420 100644 --- a/src/selector/predicate_plan.zig +++ b/src/selector/predicate_plan.zig @@ -168,8 +168,8 @@ fn compoundHash(selector: ast.Selector, comp: ast.Compound) u64 { i = 0; while (i < comp.attr_len) : (i += 1) { const item = selector.attrs[comp.attr_start + i]; - hashU64(&hash, @intFromEnum(item.op)); - hashU64(&hash, @intFromEnum(item.case)); + hashU64(&hash, @backingInt(item.op)); + hashU64(&hash, @backingInt(item.case)); hashRange(&hash, selector.source, item.name); hashRange(&hash, selector.source, item.value); } @@ -178,7 +178,7 @@ fn compoundHash(selector: ast.Selector, comp: ast.Compound) u64 { i = 0; while (i < comp.pseudo_len) : (i += 1) { const item = selector.pseudos[comp.pseudo_start + i]; - hashU64(&hash, @intFromEnum(item.kind)); + hashU64(&hash, @backingInt(item.kind)); hashU64(&hash, @bitCast(@as(i64, item.nth.a))); hashU64(&hash, @bitCast(@as(i64, item.nth.b))); } @@ -187,12 +187,12 @@ fn compoundHash(selector: ast.Selector, comp: ast.Compound) u64 { i = 0; while (i < comp.not_len) : (i += 1) { const item = selector.not_items[comp.not_start + i]; - hashU64(&hash, @intFromEnum(item.kind)); + hashU64(&hash, @backingInt(item.kind)); switch (item.kind) { .tag, .id, .class => hashRange(&hash, selector.source, item.text), .attr => { - hashU64(&hash, @intFromEnum(item.attr.op)); - hashU64(&hash, @intFromEnum(item.attr.case)); + hashU64(&hash, @backingInt(item.attr.op)); + hashU64(&hash, @backingInt(item.attr.case)); hashRange(&hash, selector.source, item.attr.name); hashRange(&hash, selector.source, item.attr.value); }, diff --git a/src/testing.zig b/src/testing.zig index 7e475a4..d8c986f 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -10,17 +10,16 @@ fn ArgsTuple(comptime Function: type) ?type { if (info != .@"fn") @compileError("ArgsTuple expects a function type"); const function_info = info.@"fn"; - if (function_info.is_var_args) return null; + if (function_info.attrs.varargs) return null; - var argument_field_list: [function_info.params.len]type = undefined; - inline for (function_info.params, 0..) |arg, i| { - if (arg.is_generic) return null; - const T = arg.type orelse return null; + var argument_field_list: [function_info.param_types.len]type = undefined; + inline for (function_info.param_types, 0..) |arg, i| { + const T = arg orelse return null; if (T == type or @typeInfo(T) == .@"fn") return null; argument_field_list[i] = T; } - return std.meta.Tuple(&argument_field_list); + return @Tuple(&argument_field_list); } fn initType(comptime T: type) T { @@ -37,26 +36,26 @@ fn initType(comptime T: type) T { .array => |ai| inline for (0..ai.len) |i| { retval[i] = initType(ai.child); }, - .@"struct" => |si| inline for (si.fields) |field| { - @field(retval, field.name) = if (field.defaultValue()) |v| v else comptime initType(@FieldType(T, field.name)); + .@"struct" => |si| inline for (si.field_names, si.field_types, si.field_attrs) |field_name, field_type, field_attrs| { + @field(retval, field_name) = if (field_attrs.defaultValue(field_type)) |v| v else comptime initType(field_type); }, .comptime_float => return 0.0, .comptime_int => return 0, .undefined => unreachable, .null, .optional => return null, .error_union => |eu| return initType(eu.payload), - .error_set => |es_| if (es_) |es| { + .error_set => |es_| if (es_.error_names) |es| { if (es.len == 0) return undefined; - return @field(T, es[0].name); + return @field(T, es[0]); } else error.AnyError, - .@"enum" => |ei| if (ei.fields.len != 0) { - retval = @field(T, ei.fields[0].name); + .@"enum" => |ei| if (ei.field_names.len != 0) { + retval = @field(T, ei.field_names[0]); } else return undefined, - .@"union" => |ui| if (ui.fields.len != 0) { - retval = @unionInit(T, ui.fields[0].name, initType(ui.fields[0].type)); + .@"union" => |ui| if (ui.field_names.len != 0) { + retval = @unionInit(T, ui.field_names[0], initType(ui.field_types[0])); }, .@"fn" => return undefined, - .@"opaque", .frame, .@"anyframe" => unreachable, + .@"opaque", .frame, .@"anyframe", .spirv => unreachable, .vector => |vi| inline for (vi.len) |i| { @field(retval, i) = initType(vi.child); }, @@ -68,19 +67,19 @@ fn initType(comptime T: type) T { pub fn refAllDeclsRecursive(comptime T: type) void { var should_run: bool = false; std.mem.doNotOptimizeAway(&should_run); - inline for (comptime std.meta.declarations(T)) |decl| { - const field = @field(T, decl.name); + inline for (comptime std.meta.declarations(T)) |decl_name| { + const field = @field(T, decl_name); _ = &field; if (@TypeOf(field) == type) { - switch (@typeInfo(@field(T, decl.name))) { - .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(@field(T, decl.name)), + switch (@typeInfo(@field(T, decl_name))) { + .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(@field(T, decl_name)), else => {}, } } else if (@typeInfo(@TypeOf(field)) == .@"fn") { // Comptime compile APIs intentionally reject fabricated input and // are covered by focused tests with meaningful source strings. - if (should_run and !comptime std.mem.startsWith(u8, decl.name, "compile")) { + if (should_run and !comptime std.mem.startsWith(u8, decl_name, "compile")) { if (ArgsTuple(@TypeOf(field))) |Args| { _ = &@call(.auto, field, comptime initType(Args)); } diff --git a/tools/bench/bench.zig b/tools/bench/bench.zig index 2e6a96f..f5b2105 100644 --- a/tools/bench/bench.zig +++ b/tools/bench/bench.zig @@ -9,7 +9,7 @@ const StreamBenchCtx = struct { fn cb(self: *@This(), event: root.StreamingParser.Event) !bool { self.events +%= 1; - self.checksum +%= @as(u64, @intFromEnum(event.kind)) + 1; + self.checksum +%= @as(u64, @backingInt(event.kind)) + 1; self.checksum +%= @as(u64, event.depth); self.checksum +%= @as(u64, event.name.start) + @as(u64, event.name.len); self.checksum +%= @as(u64, event.value.start) + @as(u64, event.value.len); diff --git a/tools/scripts.zig b/tools/scripts.zig index 4dbdc58..82ea3af 100644 --- a/tools/scripts.zig +++ b/tools/scripts.zig @@ -338,7 +338,7 @@ fn ensureExternalParsersBuilt(io: std.Io, alloc: std.mem.Allocator, include_lexb fn buildRunners(io: std.Io, alloc: std.mem.Allocator, include_lexbor: bool) !void { try common.ensureDir(io, BIN_DIR); - const zig_build = [_][]const u8{ "zig", "build", "-Doptimize=ReleaseFast", "-Dcpu=native" }; + const zig_build = [_][]const u8{ "zig", "build", "-Doptimize=fast", "-Dcpu=native" }; try common.runInherit(io, alloc, &zig_build, REPO_ROOT); const strlen_cc = [_][]const u8{ @@ -431,8 +431,8 @@ const BenchmarkEnvironment = struct { cpu_max_mhz: []const u8, fn deinit(self: BenchmarkEnvironment, alloc: std.mem.Allocator) void { - inline for (std.meta.fields(BenchmarkEnvironment)) |field| { - alloc.free(@field(self, field.name)); + inline for (comptime std.meta.fieldNames(BenchmarkEnvironment)) |field_name| { + alloc.free(@field(self, field_name)); } } }; @@ -780,7 +780,7 @@ fn compareWorktrees(io: std.Io, alloc: std.mem.Allocator, args: []const []const const fixture_dir = try std.fs.path.join(alloc, &.{ base_dir, FIXTURES_DIR }); defer alloc.free(fixture_dir); - const build_argv = [_][]const u8{ "zig", "build", "-Doptimize=ReleaseFast", "-Dcpu=native" }; + const build_argv = [_][]const u8{ "zig", "build", "-Doptimize=fast", "-Dcpu=native" }; std.debug.print("building base: {s}\n", .{base_dir}); try common.runInherit(io, alloc, &build_argv, base_dir); std.debug.print("building candidate: {s}\n", .{candidate_dir}); @@ -2223,7 +2223,7 @@ fn buildSuiteRunner(io: std.Io, alloc: std.mem.Allocator) !void { const html_mod = "-Mhtml=root.zig"; const config_path = try tempConfigModule(io, alloc); defer { - std.Io.Dir.deleteFileAbsolute(io, config_path) catch {}; + std.Io.Dir.cwd().deleteFile(io, config_path) catch {}; alloc.free(config_path); } const config_mod = try std.fmt.allocPrint(alloc, "-Mconfig={s}", .{config_path}); @@ -2239,22 +2239,23 @@ fn buildSuiteRunner(io: std.Io, alloc: std.mem.Allocator) !void { html_mod, config_mod, "-O", - "ReleaseFast", + "fast", "-femit-bin=" ++ SUITE_RUNNER_BIN, }; try common.runInherit(io, alloc, &argv, REPO_ROOT); } fn tempConfigModule(io: std.Io, alloc: std.mem.Allocator) ![]u8 { + try common.ensureDir(io, ".zig-cache"); var src: std.Random.IoSource = .{ .io = io }; - const path = try std.fmt.allocPrint(alloc, "/tmp/html-config-{x}.zig", .{src.interface().int(u64)}); + const path = try std.fmt.allocPrint(alloc, ".zig-cache/html-config-{x}.zig", .{src.interface().int(u64)}); errdefer alloc.free(path); - const file = try std.Io.Dir.createFileAbsolute(io, path, .{ + const file = try std.Io.Dir.cwd().createFile(io, path, .{ .truncate = true, .exclusive = true, }); - errdefer std.Io.Dir.deleteFileAbsolute(io, path) catch {}; + errdefer std.Io.Dir.cwd().deleteFile(io, path) catch {}; defer file.close(io); try file.writeStreamingAll(io, \\pub const IntLen = enum { @@ -3306,7 +3307,7 @@ fn runExamplesCheck(io: std.Io, alloc: std.mem.Allocator) !void { const config_path = try tempConfigModule(io, alloc); defer { - std.Io.Dir.deleteFileAbsolute(io, config_path) catch {}; + std.Io.Dir.cwd().deleteFile(io, config_path) catch {}; alloc.free(config_path); } const config_mod = try std.fmt.allocPrint(alloc, "-Mconfig={s}", .{config_path}); @@ -3572,7 +3573,10 @@ test "selector failure append frees partial ownership on allocation failure" { } test "console rendering frees partial rows on every allocation failure" { - try std.testing.checkAllAllocationFailures(std.testing.allocator, struct { + // SafeAllocator may resize in place depending on the allocation address. + // Force growth through alloc so every injected failure follows the same path. + var no_resize = std.testing.FailingAllocator.init(std.testing.allocator, .{ .resize_fail_index = 0 }); + try std.testing.checkAllAllocationFailures(no_resize.allocator(), struct { fn run(alloc: std.mem.Allocator) !void { var samples: [0]u64 = .{}; const parse_rows = [_]ParseResult{.{ diff --git a/tools/test_runner.zig b/tools/test_runner.zig index 722fb7b..1eeb76a 100644 --- a/tools/test_runner.zig +++ b/tools/test_runner.zig @@ -130,11 +130,11 @@ fn mainServer(init: std.process.Init.Minimal) !void { @disableInstrumentation(); stdin_reader = .initStreaming(.stdin(), runner_threaded_io, &stdin_buffer); stdout_writer = .initStreaming(.stdout(), runner_threaded_io, &stdout_buffer); - var server = try std.zig.Server.init(.{ + var server: std.zig.Server = .{ .in = &stdin_reader.interface, .out = &stdout_writer.interface, - .zig_version = builtin.zig_version_string, - }); + }; + try server.serveStringMessage(.zig_version, builtin.zig_version_string); while (true) { const hdr = try server.receiveMessage(); @@ -143,8 +143,8 @@ fn mainServer(init: std.process.Init.Minimal) !void { return std.process.exit(0); }, .query_test_metadata => { - testing.allocator_instance = .{}; - defer if (testing.allocator_instance.deinit() == .leak) { + testing.allocator_instance = .init(std.heap.page_allocator, .{ .check_write_after_free = true }); + defer if (testing.allocator_instance.deinit() != 0) { @panic("internal test runner memory leak"); }; @@ -175,7 +175,7 @@ fn mainServer(init: std.process.Init.Minimal) !void { .run_test => { testing.environ = init.environ; - testing.allocator_instance = .{}; + testing.allocator_instance = .init(std.heap.page_allocator, .{ .check_write_after_free = true }); testing.io_instance = .init(testing.allocator, .{ .argv0 = .init(init.args), .environ = init.environ, @@ -202,8 +202,7 @@ fn mainServer(init: std.process.Init.Minimal) !void { }, }; testing.io_instance.deinit(); - const leak_count = testing.allocator_instance.detectLeaks(); - testing.allocator_instance.deinitWithoutLeakChecks(); + const leak_count = testing.allocator_instance.deinit(); try server.serveTestResults(.{ .index = index, .flags = .{ @@ -225,8 +224,8 @@ fn mainServer(init: std.process.Init.Minimal) !void { // since they are not present. if (!builtin.fuzz) unreachable; - var gpa_instance: std.heap.DebugAllocator(.{}) = .init; - defer if (gpa_instance.deinit() == .leak) { + var gpa_instance: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); + defer if (gpa_instance.deinit() != 0) { @panic("internal test runner memory leak"); }; const gpa = gpa_instance.allocator(); @@ -237,7 +236,7 @@ fn mainServer(init: std.process.Init.Minimal) !void { defer io_instance.deinit(); const io = io_instance.io(); - const mode: fuzz_abi.LimitKind = @enumFromInt(try server.receiveBody_u8()); + const mode: fuzz_abi.LimitKind = @fromBackingInt(@intCast(try server.receiveBody_u8())); const amount_or_instance = try server.receiveBody_u64(); const main_instance = mode == .iterations or amount_or_instance == 0; @@ -299,7 +298,7 @@ fn mainServer(init: std.process.Init.Minimal) !void { }, else => { - std.debug.print("unsupported message: {x}\n", .{@intFromEnum(hdr.tag)}); + std.debug.print("unsupported message: {x}\n", .{@backingInt(hdr.tag)}); std.process.exit(1); }, } @@ -548,7 +547,7 @@ fn printTestOutput(ctx: *const WorkerCtx, name: []const u8, res: ChildResult) vo } switch (res.term) { .exited => |code| if (code != 0) w.print(" | exit {d}", .{code}) catch return, - .signal => |sig| w.print(" | signal {d}", .{@intFromEnum(sig)}) catch return, + .signal => |sig| w.print(" | signal {d}", .{@backingInt(sig)}) catch return, .stopped => |code| w.print(" | stopped {d}", .{code}) catch return, .unknown => |code| w.print(" | unknown {d}", .{code}) catch return, } @@ -560,7 +559,7 @@ fn printTestOutput(ctx: *const WorkerCtx, name: []const u8, res: ChildResult) vo w.print("{s}{s}\x1b[0m {s}", .{ color, label, name }) catch return; switch (res.term) { .exited => |code| if (code != 0) w.print(" | exit {d}", .{code}) catch return, - .signal => |sig| w.print(" | signal {d}", .{@intFromEnum(sig)}) catch return, + .signal => |sig| w.print(" | signal {d}", .{@backingInt(sig)}) catch return, .stopped => |code| w.print(" | stopped {d}", .{code}) catch return, .unknown => |code| w.print(" | unknown {d}", .{code}) catch return, } @@ -637,12 +636,12 @@ fn runSingleTest(init: std.process.Init.Minimal, name: []const u8, seed: ?u32) v }); defer testing.io_instance.deinit(); - testing.allocator_instance = .{}; + testing.allocator_instance = .init(std.heap.page_allocator, .{ .check_write_after_free = true }); log_err_count = 0; const result = test_fn.func(); const leak_status = testing.allocator_instance.deinit(); - if (leak_status == .leak) { + if (leak_status != 0) { std.debug.print("memory leak\n", .{}); std.process.exit(3); } @@ -678,10 +677,10 @@ pub fn log( args: anytype, ) void { @disableInstrumentation(); - if (@intFromEnum(message_level) <= @intFromEnum(std.log.Level.err)) { + if (@backingInt(message_level) <= @backingInt(std.log.Level.err)) { log_err_count +|= 1; } - if (@intFromEnum(message_level) <= @intFromEnum(testing.log_level)) { + if (@backingInt(message_level) <= @backingInt(testing.log_level)) { std.debug.print( "[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n", args, @@ -762,8 +761,8 @@ var fuzz_runner: if (builtin.fuzz) struct { error.WriteFailed => panic("failed to write to stdout: {t}", .{stdout_writer.err.?}), }; - testing.allocator_instance = .{}; - defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1); + testing.allocator_instance = .init(std.heap.page_allocator, .{ .check_write_after_free = true }); + defer if (testing.allocator_instance.deinit() != 0) std.process.exit(1); is_fuzz_test = false; builtin.test_functions[fuzz_runner.indexes[i]].func() catch |err| switch (err) { @@ -837,7 +836,7 @@ var fuzz_runner: if (builtin.fuzz) struct { while (true) { const hdr = try server.receiveMessage(); if (hdr.tag != .new_fuzz_input) { - panic("unexpected message: {x}\n", .{@intFromEnum(hdr.tag)}); + panic("unexpected message: {x}\n", .{@backingInt(hdr.tag)}); } const test_i = try server.receiveBody_u32(); const input_len = hdr.bytes_len - 4; @@ -886,8 +885,8 @@ pub fn fuzz( fn test_one() callconv(.c) bool { @disableInstrumentation(); - testing.allocator_instance = .{}; - defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1); + testing.allocator_instance = .init(std.heap.page_allocator, .{ .check_write_after_free = true }); + defer if (testing.allocator_instance.deinit() != 0) std.process.exit(1); log_err_count = 0; testOne(ctx, @constCast(&testing.Smith{ .in = null })) catch |err| switch (err) { error.SkipZigTest => return true, @@ -914,7 +913,7 @@ pub fn fuzz( if (builtin.fuzz) { // Preserve the calling test's allocator state const prev_allocator_state = testing.allocator_instance; - testing.allocator_instance = .{}; + testing.allocator_instance = .init(std.heap.page_allocator, .{ .check_write_after_free = true }); defer testing.allocator_instance = prev_allocator_state; global.ctx = context; diff --git a/tools/tests/public_api_tests.zig b/tools/tests/public_api_tests.zig index 2108900..05b7d07 100644 --- a/tools/tests/public_api_tests.zig +++ b/tools/tests/public_api_tests.zig @@ -10,9 +10,9 @@ fn assertPublicFunctions(comptime T: type, comptime expected: []const []const u8 @setEvalBranchQuota(1000_000); comptime { for (std.meta.declarations(T)) |decl| { - const value = @field(T, decl.name); - if (@typeInfo(@TypeOf(value)) == .@"fn" and !contains(expected, decl.name)) { - @compileError("public API function lacks explicit coverage: " ++ @typeName(T) ++ "." ++ decl.name); + const value = @field(T, decl); + if (@typeInfo(@TypeOf(value)) == .@"fn" and !contains(expected, decl)) { + @compileError("public API function lacks explicit coverage: " ++ @typeName(T) ++ "." ++ decl); } } for (expected) |name| {