← board

C compound literals (struct S){...} — file scope SIGSEGVs, init battery fails

Failing tests

Needed

C99 6.5.2.5: compound literal = anonymous object; at file scope static storage duration (address is a link-time constant), at block scope automatic. Parser accepts something today (149/150 COMPILE then crash) — likely treated as cast of brace list producing garbage pointer.

Gate

Drop 00149.c/00150.c/00216.c from test/c-conformance/pxx.skip; runner green.

Triage 2026-07-07

Not implemented in any position: (struct S){1,2} as a local/inline expression -> CERR "expected C expression" (parsed as a cast, then the { derails); the file-scope &(struct S){...} parses but SIGSEGVs. Needs: (1) ParseCPrimary/cast path to disambiguate (type){...} (compound literal) from (type)expr (cast); (2) materialize an anonymous object — static storage at file scope, automatic at block scope — initialize it (reuse the braced-init machinery), and yield its value / address. Multi-part feature, focused session.

Parser hook located 2026-07-08 (a-agent) — the reuse blocker

The disambiguation point is ParseCUnary's cast branch (cparser.inc ~1447): after if (CurTok.Kind = tkLParen) and CIsCastAhead parses castTk := ParseCDeclType and Expect(tkRParen), a following CurTok.Kind = tkBegin ({) means COMPOUND LITERAL, not a cast — today it falls into ParseCUnary() which sees { and derails. Add: if CurTok.Kind = tkBegin then <materialize + init + yield lvalue>.

The real blocker is REUSE: the brace-init machinery (record field init, array element init, nested braces, zero-fill tail) lives INLINE inside ParseCLocalDeclAST (~2760-2920) and ParseCGlobalVarDecl, not as a callable "init this lvalue/symbol from the brace list at TokPos" helper. A clean compound- literal impl wants that factored out first (a CParseBraceInitInto(symIdx, tk, recId) returning an init AST), then:

Progress 2026-07-08 (a-agent) — 00149 + 00150 FIXED (file-scope), 00216 remains

File-scope T *p = &(RecType){...} implemented in ParseCGlobalVarDecl: a new &(...) branch materialises an anonymous static record object (name keyed by the brace token position so both driver passes dedup to one object), initialises it via the existing deferred aggregate walker (CAggInit / CEmitDeferredCAggInits — same path a named struct S g = {...} global uses, so positional AND nested designated bodies work), then binds p to its address (PendingInit Elem=-2 address-of-sym). 00149 (positional) and 00150 (nested designated {.b=2,.a=1}, &gs1, array member {[0]=1,1+1}) both GREEN. Self-host byte-identical, quick tier green, conformance 208 pass / 0 fail / 12 skip (was 206). Dropped 00149/00150 from pxx.skip.

Remaining (ticket stays open):

2026-07-09 (A+B+C agent) — RESOLVED: c-testsuite 00216 GREEN, conformance 220/220/0

00216 is byte-identical to the gcc oracle; dropped from pxx.skip; make test-c-conformance = 220 pass / 0 fail / 0 skip. The task's premise ("only inline compound literals block 00216") was wrong — reaching 00216 green took SEVEN fixes, each gcc-verified + self-host byte-identical + a test-core regression (b216-b225):

  1. Record compound literals (T){...} — base node AN_COMPOUND_LITERAL=81 + gaps A (postfix), B (whole-record-value elements + CInitPeekElemTk/CAggCurSym/CInitPath save-restore), C (file-scope CL in global array). (b216-b219)
  2. C lexer form-feed/vtab whitespace (#12/#11) — the "cumulative desync" ghost. (b220)
  3. Anonymous struct/union member braced-designated init — UFldAnonRec group marker, walker re-groups a promoted anon member for an inner brace. (b221)
  4. Redundant struct-to-struct cast (struct S)x = identity, not AN_PTR_CAST retag (retag of an address-carried record SEGV'd on copy). (b222)
  5. Range designator [lo...hi] must not inflate a fn-ptr array's length (sizeof doubled -> t[i]() ran off the end into NULL -> SIGSEGV). (b223)
  6. Flexible array member T x[] is 0-size in sizeof (was parsed as [1]). (b224)
  7. File-scope struct var initialized by a compound literal = (T){...} / = ((T){...}) (was left zero). (b225)

Also satisfies [[feature-c-designated-init-compound-literals]] record-CL acceptance and resolves [[bug-c-anonymous-member-designated-init]] + [[bug-c-fullfile-cumulative-parser-desync]]. Pinned (compiler changed). Cross targets untouched (frontend/IR only). RESOLVED — moving to done.

2026-07-09 (A+B+C agent) — COMPOUND LITERALS COMPLETE; 00216 blocked by TWO pre-existing NON-CL bugs (superseded — all resolved, see above)

Inline compound literals (T) + braced init are now fully implemented and pinned (the AN_COMPOUND_LITERAL node from the ATTEMPT note, re-applied + all 3 gaps closed). Each gcc-verified (exit 42) + self-host byte-identical + regression test in test-core:

Conformance held at 219/0/1 (no regression). feature-c-designated-init-compound- literals' compound-literal acceptance is satisfied by this work.

00216 is NOT yet green — it needs TWO further, PRE-EXISTING, non-CL features

Retested with the CL work in: 00216 no longer desyncs at global_wrap, but still fails. Both remaining blockers reproduce on the PINNED v185 binary (i.e. they predate and are unrelated to compound literals):

  1. Anonymous struct/union member braced-designated init (-fms-extensions): union UV {struct {u8 a,b;}; struct S s;}; union UV g = {{.b=7,.a=8}}; → "expected C expression". The anon struct's fields are PROMOTED flat into the union, so the walker treats the union's field[0] as scalar a, sees the inner brace as a braced-scalar, and chokes on the .b designator. Needs the walker to treat a promoted anonymous aggregate as one member for a matching inner brace. (Positional {{6,5}} and union-level promoted designators {.b=8,.a=7} already work.)
  2. Cumulative parser desync (brace/state imbalance detected at EOF as "stray token at top level" with empty SVal): appears only when NEARLY ALL of 00216's functions are present together (each subset — tcwr, table+multi, SE+zeroinit, foo — compiles fine alone or in pairs; the full set desyncs by one). Not a fixed-array cap (only 15 types; caps are 2048+). A pre-existing accumulation bug in the two-pass driver. → Filed as separate tickets; 00216 stays on pxx.skip until both land. See [[bug-c-anonymous-member-designated-init]] and [[bug-c-fullfile-cumulative-parser-desync]].

2026-07-09 STATUS — 00216 is 4/5 done; ONLY inline compound literals remain (v185)

Pieces 1-4 + piece 3's designators all landed and pinned (v180→v185), each gcc-verified + self-host byte-identical + regression test in test-core. The single remaining blocker for c-testsuite 00216 (→ 220/220) is piece 5: inline compound literals (T){...} — needs an AN_COMPOUND_LITERAL IR node (see the ATTEMPT note below; the parser-comma hack SEGVs). That node also unblocks 00216's file-scope global_wrap[] and closes feature-c-designated-init-compound-literals' compound-literal acceptance. Detailed 5-piece map + status follows.

2026-07-09 (A+B+C agent) — 00216 mapped to 5 sub-features across 4 init paths; range designators LANDED

Retested 00216 at HEAD (v179). It is NOT one gap — it needs 5 distinct sub-features, spread over 4 SEPARATE initializer code paths, each self-host- critical. Precise map (minimal repros each confirmed):

  1. Range designators [lo ... hi] = v — the recursive aggregate-init walker (CInitWalkArray, the [ designator branch). DONE this session: expands the range, re-seeking the one value's tokens per index; covers local + global struct-member arrays (both route through the walker via CEmitDeferredCAggInits). gcc-verified, test/crange_designator_b210.c → exit 42. Overlapping ranges resolve left-to-right.
  2. Range designators in the GLOBAL SCALAR-array path — a DIFFERENT loop (ParseCGlobalVarDecl ~5352) gated by CBraceFlatIntInitCountAt, which rejects [lo...hi] (only [int]). int g[4]={[0...2]=7} silently zero-fills. TODO: accept the range in the flat-int scanner + the emit loop.
  3. Global FUNCTION-POINTER arraystypedef arrays DONE 2026-07-09: fptr t[N] = {a,b} / unsized fp t[] = {&sq,&dbl} now work. The fn-ptr global path only recognised INLINE declarators (*t[N])(); a typedef array left the trailing [N] after the name unconsumed (desync). Now that [N] is parsed (wasArr set) and &func element prefixes accepted; the branch already emits arrKind=3 proc-address PendingInits (Kind=2 → AN_PROCADDR). gcc-verified, test/cfnptr_typedef_array_b214.c → exit 42, 219/0/1. Designators + ranges in the fn-ptr scanner DONE 2026-07-09 (v185): the scanner tracks a per-element target index (arrTgt[]) and replicates a range's proc across [lo ... hi]; later single [k]= designators override earlier range slots (PendingInit last-wins). The exact 00216 reloc table {[0 ... 2]=&sni, [0]=one, [1]=two, [2]=three} now materialises correctly (prints one/two/three). gcc-verified, test/cfnptr_range_table_b215.c → exit 42. Piece 3 COMPLETE.
  4. Nested designators .a.j = vDONE 2026-07-09: after .name positions to a field, a continuation .sub/[i] now descends the full chain via CInitDesignatedDescend (pushes path frames, inits the designated leaf or braced subobject), then resumes positionally. Handles record.record.scalar (.a.j), 3-level (.b.c.y), reordered, local + global. gcc-verified, test/cnested_designator_b213.c → exit 42, self-host byte-identical, 219/0/1.
  5. Inline compound literals (T){...} as expressionsParseCUnary cast branch (~1719): after castTk=ParseCDeclType; Expect(')'), a following { is a compound literal, today it recurses into ParseCUnary and derails. The blocker is REUSE: materialize an anonymous object (automatic at block scope, static at file scope), init it via the brace machinery, yield an lvalue — needs the local brace-init extracted into a callable CParseBraceInitInto. Self-host-fragile (shared init walker). Once this lands, the walker's EMIT-mode leaf (which uses ParseCExpr) gets compound-literal VALUES + &func + casts for free, unblocking {((struct Wrap){inc}), inc} and .a=(struct A){1,2}.

Order to finish: (2)+(3) global paths, (4) nested designators, then (5) compound literals (biggest + riskiest). (1) is committed. Remaining pieces parked in backlog — each a focused, self-host-verified change.

Block-scope compound literals — AN_COMPOUND_LITERAL IR node PROVEN for flat cases 2026-07-09

Second attempt (the right layer). Added an AN_COMPOUND_LITERAL AST node (defs.inc = 81) and integrated it — this WORKS for flat block-scope compound literals and is the design the next session should re-apply:

VERIFIED vs gcc (all exit 42): (struct P){3,4} as a by-value arg; &(struct P){1,2,3}

REVERTED (kept v185 clean) because three cases were still wrong and one SILENTLY miscompiled — must not land a silent-wrong path. Remaining before it can land:

  1. Postfix (T){...}.field / [i] — ParseCUnary's cast branch Exits, so the CL result skips ParseCPostfix → "expected C expression". Route the CL node back through the postfix-tail so .f/->/[i] chain onto it.
  2. Nested CL as a designated sub-value (struct Out){.a = (struct In){...}} — SILENTLY WRONG: the outer walker descends into .a's fields and misassigns the inner CL. The walker's leaf/member path must detect a whole-record value (CL or struct rvalue) for a record-typed field and assign it WHOLE (IR_COPY_REC) instead of descending. (Same "whole-aggregate-value element" gap that .daddr = phdr->daddr needs.)
  3. File-scope CL in a global array (00216 global_wrap[]) — SEGV: the global record-array walker replays at main via ParseCExpr; a CL temp AllocVar'd during that deferred emit has the wrong CurProc/storage context. File-scope CLs have static storage (C99) — likely needs the static-anon-object path (like done 00149/00150 &(T){...}), not the automatic block-scope temp.

Block-scope compound-literal ATTEMPT 1 2026-07-09 — parser-comma approach FAILS at IR lowering

Tried the obvious block-scope impl in ParseCUnary's cast branch: on (recTk){ materialise tmpSym := AllocVar('',tyRecord) (LastTypeRecId=recId), zero via CMakeZeroLocal, init via CInitLocalAggregate (chain mode), then splice the zero+element-store AN_ASSIGNs and a terminal AN_IDENT into a right-nested AN_COMMA so the whole thing is one expression yielding the object. Compiles + self-hosts, but at RUNTIME:

Assessment 2026-07-08 (cfront-agent) — released; remaining work is deep, needs the factor-out

Confirmed the remaining two pieces are NOT bounded wire-ups: