Skip to content

Empty array literal emits invalid Zig — 227 of 656 compile failures, one construct #3960

Description

@gHashTag

pub const SKILLS : [0]str = []; generates pub const SKILLS: [0]str = [];.
Zig rejects that outright:

agents/c.zig:23:30: error: expected type expression, found ';'
pub const SKILLS: [0]str = [];
                             ^

Valid Zig is .{}. The literal passes through to the output verbatim.

Scale

Measured 2026-09-17 on master (0ad9696), generating all 945 specs and running
each under zig test with a t27c built from that same commit:

count
specs that compile and pass their tests 279
specs that do not compile 656
specs that compile, run, and hit an unimplemented body 10

227 of those 656 contain an emitted = [];, and all 227 fail to compile.
That is 35% of every compile failure in the corpus, from four characters.
It is the single largest defect class by a wide margin — the next is
159 use of undeclared identifier, spread across unrelated causes.

Why the parser rejects it today

parse_bare_array_literal bails on [ immediately followed by ]:

if self.peek.kind == TokenKind::RBracket {
    return None;
}

That guard is correct for a slice TYPE ([]u8) and keeps the pass linear. It is
wrong for a VALUE, and the two are distinguishable with one more token: a slice
type always names what it is a slice of, so [ ] followed by anything that is
not an identifier or another [ can only be an empty list.

A single-element literal ([7]) is a separate and genuinely ambiguous case —
indistinguishable from a dimension — and this patch deliberately leaves it alone.

The patch

Two hunks: one at the parser guard, one so an array literal with no children and
no element text emits .{} rather than falling into the comma-splitting path and
producing a phantom element.

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 {

Why this is filed rather than pushed

bootstrap/src/compiler.rs is under the FROZEN_HASH seal, and FROZEN.md §5
step 2 requires a PR marked [GOLD-RING] with a milestone, or an
Architect-approved hotfix. That is a human decision and not one an automated
cycle should make. The patch above is applied, built and verified nowhere except
in a scratch worktree, which was then reverted.

Run the ceremony and the expected result is measurable in one command:
queen oracle should move 227 specs out of NOCOMPILE.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions