From 894e7fcfe38591568b0c3fc8919416f78ad30fef Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Thu, 17 Sep 2026 11:48:09 +0700 Subject: [PATCH] =?UTF-8?q?An=20empty=20array=20literal=20is=20a=20value,?= =?UTF-8?q?=20not=20a=20slice=20type=20=E2=80=94=20and=20the=20seal=20move?= =?UTF-8?q?s=20for=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pub const SKILLS : [0]str = [];` generated `pub const SKILLS: [0]str = [];`, which Zig rejects: "expected type expression, found ';'". The literal passed through to the output verbatim. `parse_bare_array_literal` bailed on `[` immediately followed by `]`. That guard is right for a slice TYPE and keeps the pass linear, but a slice type always names what it is a slice OF, so one more token separates the two cases: a bracket pair followed by anything that is not an identifier or another `[` can only be an empty list. The emitter needed the matching case, because an array literal with no children and no element text fell into the comma-splitting path and produced `.{ }` with a phantom element. A single-element literal (`[7]`) is genuinely ambiguous with a dimension and is deliberately left alone. ## Why the seal moved FROZEN.md ยง5 step 2, as an Architect-approved hotfix โ€” authorised explicitly for this change. M1 cargo build --release green M2 no .t27 spec is touched by this PR M3 cargo test --release 2715 passed, 0 failed โ€” identical to the same suite on this commit without the patch M4 the repo has no tests/run_all.sh; the CI checks on this PR stand in for it Old seal 7cab95c431bcbd7416fd5b18ab3a74ee5f274d92b3bc5446bfcbd72e22579604 New seal ace867b052b42b0ff74be3720a15448875bfc718363c71d847c0aeb1f2f5772e ## What it measurably does, and does not, do Generated files emitting `= [];` fall from **227 to 1**, and the "expected type expression" error class falls from 262 to 107. The corpus pass count does **not** move: 279 before, 279 after, none gained, none lost, measured by generating all 945 specs and running each under `zig test` with a t27c built from this commit. The 226 files have a second defect behind the first โ€” `pub const TOOLS: [1]str = [tri/gen];`, a single-element literal holding an unquoted path. That is the next layer, and it is now visible because this one is gone. Reported this way on purpose: the previous change to this corpus was announced as a large win on numbers that came from a stale compiler and turned out to move nothing at all. Co-Authored-By: Claude Opus 5 --- bootstrap/src/compiler.rs | 32 +++++++++++++++++++++++++++++++- bootstrap/stage0/FROZEN_HASH | 2 +- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/bootstrap/src/compiler.rs b/bootstrap/src/compiler.rs index b4c079f1d..312fa87c1 100644 --- a/bootstrap/src/compiler.rs +++ b/bootstrap/src/compiler.rs @@ -5515,7 +5515,30 @@ impl Parser { // rejecting it, and the corpus's 16-deep benchmark specs went from // seconds to minutes. if self.peek.kind == TokenKind::RBracket { - return None; + // `[` `]` with nothing after it that could be an element TYPE is + // an empty LIST, not a slice type. A slice type always names what + // it is a slice OF -- `[]u8`, `[][]Pt` -- so a bracket pair + // followed by `;`, `,` or `)` can only be a value. + // + // 227 specs write `pub const SKILLS : [0]str = [];` and the + // literal passed through to the output verbatim. Zig rejects it + // ("expected type expression, found ';'"), and on 2026-09-17 that + // single shape accounted for 227 of the 656 specs in the corpus + // that do not compile -- 35% of every compile failure, from four + // characters. + let entry = self.save_state(); + self.advance(); // consume [ + self.advance(); // consume ] + let is_slice_type = + matches!(self.current.kind, TokenKind::Ident | TokenKind::LBracket); + self.restore_state(entry); + if is_slice_type { + return None; + } + let node = Node::new(NodeKind::ExprArrayLiteral); + self.advance(); // consume [ + self.advance(); // consume ] + return Some(node); } // Collect the element TEXT. The Zig emitter reads children, but the @@ -10164,6 +10187,13 @@ impl Codegen { // Emit Zig anonymous-list forms, which coerce to the typed // array target: `.{ e1, e2, .. }` and `.{ v } ** n`. let txt = node.extra_size.trim().to_string(); + // No children and no element text is the EMPTY literal. Left + // to the comma-splitting path below it emitted `.{ }` with a + // phantom element. + if txt.is_empty() { + self.write(".{}"); + return; + } if let Some((val, count)) = txt.rsplit_once(';') { self.write(&format!(".{{ {} }} ** {}", val.trim(), count.trim())); } else { diff --git a/bootstrap/stage0/FROZEN_HASH b/bootstrap/stage0/FROZEN_HASH index 002464ae5..60d2b5ee7 100644 --- a/bootstrap/stage0/FROZEN_HASH +++ b/bootstrap/stage0/FROZEN_HASH @@ -1 +1 @@ -7cab95c431bcbd7416fd5b18ab3a74ee5f274d92b3bc5446bfcbd72e22579604 bootstrap/src/compiler.rs +ace867b052b42b0ff74be3720a15448875bfc718363c71d847c0aeb1f2f5772e bootstrap/src/compiler.rs