Repository navigation
t27c gen-c: identifiers named for C keywords are emitted verbatim, so the C does not compile (short, int, signed, default, ...) #10054
Description
Summary
t27c gen-c writes .t27 identifiers into C unchanged. When a local or parameter has the same name as a C keyword (short, int, char, register, default, signed, ...), gen-c exits 0, and the C it wrote fails to compile. The C backend has no keyword escaping. The Zig backend escapes keywords as @"name" (bootstrap/src/compiler.rs, around line 8642, #6451), and the Verilog backend has verilog_keywords() (around line 11505). C has neither.
Found while building the queen keyed_batch wasm card. specs/queen/keyed_batch.t27 (#10051) had a local named short, which has since been renamed to rest. Its C failed under zig cc --target=wasm32-wasi.
Repro (Railway fs-lab, not the Mac)
Built t27c at master 38b4f74c1 (cargo build --release -p t27c in a fresh worktree on the lab). Zig is 0.16.0, and its default C standard is C17 (__STDC_VERSION__ 201710L).
Each probe spec is one of these two shapes, with KW replaced by the name under test:
module CKwProbe;
pub fn probe(x: u32) -> u32 {
let KW : u32 = x + 1;
return KW;
}
test probe_adds_one {
assert probe(1) == 2;
}
The second shape uses the name as a parameter instead: pub fn probe(KW: u32) -> u32 { return KW + 1; }.
t27c gen-c local_short.t27 > local_short.c # exit 0
zig cc --target=wasm32-wasi -c local_short.c -o local_short.o
local_short.c:28:14: error: 'short type-name' is invalid
28 | uint32_t short = (x + 1);
Names that collide (measured: 39 fail under C17, 45 with -std=c23; local and parameter both fail; gen-c exits 0 every time)
C keywords, default std (C17), 33 names:
auto char const case default do double extern float goto inline int long register restrict short signed sizeof static typedef union unsigned volatile _Alignas _Alignof _Atomic _Bool _Complex _Generic _Imaginary _Noreturn _Static_assert _Thread_local
Macros from the headers gen-c includes (stdbool.h, assert.h, stddef.h) plus the GNU keyword typeof, 6 names:
bool true false static_assert NULL typeof
C23 keywords. These pass under the default C17 and fail with -std=c23, 6 names:
alignas alignof constexpr nullptr thread_local typeof_unqual
The t27 parser already rejects these, so they never reach the C backend:
enum struct switch while for if else return break continue void. (const, true and false are t27 keywords, but they are accepted as local and parameter names, and then they fail in C.)
Pass as local and parameter names: errno assert main strlen printf abs rest. Shadowing a libc name inside a function is legal C. They were not tested as top-level fn, const or struct-field names, where they could clash with the libc declarations.
Not tested: fn names, struct fields, module consts and enum variants named for C keywords. These are likely the same defect, since every identifier is emitted unchanged.
Full table (126 rows: kind, name, gen-c result, zig cc result, first error): attached below.
Corpus exposure
A grep of specs/**/*.t27 at 38b4f74c1 for these names used as a let/var/const binding or a parameter finds 94 sites in 32 specs. By name: signed 55, default 22, short 6, bool 4, long 3, char 3, float 2, case 2, unsigned 1. Examples: specs/effects/capability.t27:386 (var long), :394 (var short), specs/t27f/reel.t27:665 (const short), and specs/tri/t27b/*_plan.t27 (signed: bool parameters). The gen-c output of every one of these specs is C that does not compile. The C-header gate (cc_gate.rs, W585) should count them under a keyword class.
Scope note: bootstrap/src/compiler.rs is existing Rust (the CCodegen emitter). Touching it needs the owner's owner-approved-foreign label. specs/compiler/c_keyword_idents.t27 is a new witness spec, next to the existing specs/compiler/c_*.t27 C-backend witnesses.
Boundary
- bootstrap/src/compiler.rs
- specs/compiler/c_keyword_idents.t27
User Scenarios
- Given a spec with a local named
short(let short : u32 = x + 1; return short;), when a bee runst27c gen-cand thenzig cc --target=wasm32-wasi -con the output, then the C compiles, and the test block still assertsprobe(1) == 2. - Given a spec with a parameter named for a C keyword (
fn f(signed: bool), as inspecs/tri/t27b/bit_cast_plan.t27), when gen-c lowers it, then the declaration and every use carry the same escaped name, and the C compiles. - Given an identifier that is not a C keyword or reserved macro (
rest,strlen), when gen-c lowers it, then it is emitted unchanged, so existing generated C does not churn.
Requirements
- FR-001:
t27c gen-cMUST NOT emit a user identifier that is a C17 or C23 keyword, or a macro from a header the generated C includes (bool true false static_assert NULL), or the GNU keywordtypeof. - FR-002: The escape MUST be applied by one function at every place the C backend writes a user identifier: locals, parameters, fn names, struct fields, module consts and enum members. A declaration and its uses MUST agree.
- FR-003: Identifiers that do not collide MUST be emitted unchanged.
- FR-004: The fix MUST NOT reject these names at parse or typecheck time. They are valid t27 in 32 existing specs and already compile through the Zig and Verilog backends.
- FR-005: The change to
bootstrap/src/compiler.rsMUST carry the owner'sowner-approved-foreignlabel before it merges.
Where the fix belongs
In the bootstrap compiler's Rust C backend (CCodegen, bootstrap/src/compiler.rs, around line 19102), not in a spec-side rule.
- A policy that rejects C keywords as .t27 identifiers would break 32 specs that are valid t27 and already compile through the Zig and Verilog backends. It would make one backend's reserved words part of the language. The other two backends solved the same problem by escaping, and C should match.
- The fix is a C identifier escape (for example, a trailing
_or at27_prefix for any name in the list above), applied at every placeCCodegenwrites a user identifier: locals, parameters, fn names, struct fields, consts and enum members. Uses and declarations must go through the same function, aszig_value_identdoes (Zig: quote declared primitive value bindings without quoting builtin types #6040). - To keep the keyword list in t27 rather than as a hand-written Rust array, the table could live in a .t27 spec and be generated into the crate, like
bootstrap/src/memory/generated/. Either way,CCodegenitself is existing Rust.
This change edits existing Rust, so it needs the owner's owner-approved-foreign label. No Rust has been written for this issue. Until the label is applied, the workaround is the one #10051 used: rename the identifier in the spec.
Success Criteria (done when)
-
t27c gen-con every probe above produces C that compiles underzig cc --target=wasm32-wasi(C17 and-std=c23). - The 32 corpus specs listed above no longer fail the C-header gate with a keyword-class error.
- A witness spec with keyword-named locals, params, fields and fns is in the cc-gate set.
Full probe table (zig 0.16.0, wasm32-wasi, default std)
kind name gen_c zig_cc_wasm32_wasi first_error
local short ok FAIL error: 'short type-name' is invalid
local int ok FAIL error: cannot combine with previous 'type-name' declaration specifier
local char ok FAIL error: cannot combine with previous 'type-name' declaration specifier
local register ok FAIL error: expected identifier or '('
local auto ok FAIL error: expected identifier or '('
local case ok FAIL error: expected identifier or '('
local default ok FAIL error: expected identifier or '('
local do ok FAIL error: expected identifier or '('
local double ok FAIL error: cannot combine with previous 'type-name' declaration specifier
local extern ok FAIL error: expected identifier or '('
local float ok FAIL error: cannot combine with previous 'type-name' declaration specifier
local goto ok FAIL error: expected identifier or '('
local inline ok FAIL error: expected identifier or '('
local long ok FAIL error: 'long type-name' is invalid
local restrict ok FAIL error: expected identifier or '('
local signed ok FAIL error: 'type-name' cannot be signed or unsigned
local sizeof ok FAIL error: expected identifier or '('
local static ok FAIL error: expected identifier or '('
local typedef ok FAIL error: expected identifier or '('
local union ok FAIL error: declaration of anonymous union must be a definition
local unsigned ok FAIL error: 'type-name' cannot be signed or unsigned
local volatile ok FAIL error: expected identifier or '('
local const ok FAIL error: expected identifier or '('
local enum FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwEnum ('enum') at line 7:1
local struct FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwStruct ('struct') at line
local switch FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Expected LParen, got Semicolon (';') at line 7:18
local while FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwWhile ('while') at line 7
local for FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwFor ('for') at line 7:12
local if FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: Semicolon (';') at line 7:1
local else FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwElse ('else') at line 7:1
local return FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwReturn ('return') at line
local break FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwBreak ('break') at line 7
local continue FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwContinue ('continue') at
local void FAIL - Error: Compile error: parse error in fn 'probe' near line 7: Unexpected token in expression: KwVoid ('void') at line 7:1
local _Bool ok FAIL error: cannot combine with previous 'type-name' declaration specifier
local _Alignas ok FAIL error: expected '('
local _Alignof ok FAIL error: expected identifier or '('
local _Atomic ok FAIL error: expected identifier or '('
local _Complex ok FAIL error: '_Complex type-name' is invalid
local _Generic ok FAIL error: expected identifier or '('
local _Imaginary ok FAIL error: '_Complex type-name' is invalid
local _Noreturn ok FAIL error: expected identifier or '('
local _Static_assert ok FAIL error: expected identifier or '('
local _Thread_local ok FAIL error: expected identifier or '('
local bool ok FAIL error: cannot combine with previous 'type-name' declaration specifier
local true ok FAIL error: expected identifier or '('
local false ok FAIL error: expected identifier or '('
local alignas ok ok
local alignof ok ok
local constexpr ok ok
local nullptr ok ok
local static_assert ok FAIL error: expected identifier or '('
local thread_local ok ok
local typeof ok FAIL error: expected '(' after 'typeof'
local typeof_unqual ok ok
local NULL ok FAIL error: expected identifier or '('
local errno ok ok
local assert ok ok
local main ok ok
local strlen ok ok
local printf ok ok
local abs ok ok
local rest ok ok
param short ok FAIL error: 'short type-name' is invalid
param int ok FAIL error: cannot combine with previous 'type-name' declaration specifier
param char ok FAIL error: cannot combine with previous 'type-name' declaration specifier
param register ok FAIL error: expected expression
param auto ok FAIL error: invalid storage class specifier in function declarator
param case ok FAIL error: invalid parameter name: 'case' is a keyword
param default ok FAIL error: invalid parameter name: 'default' is a keyword
param do ok FAIL error: invalid parameter name: 'do' is a keyword
param double ok FAIL error: cannot combine with previous 'type-name' declaration specifier
param extern ok FAIL error: invalid storage class specifier in function declarator
param float ok FAIL error: cannot combine with previous 'type-name' declaration specifier
param goto ok FAIL error: invalid parameter name: 'goto' is a keyword
param inline ok FAIL error: 'inline' can only appear on functions
param long ok FAIL error: 'long type-name' is invalid
param restrict ok FAIL error: restrict requires a pointer or reference ('uint32_t' (aka 'unsigned int') is invalid)
param signed ok FAIL error: 'type-name' cannot be signed or unsigned
param sizeof ok FAIL error: invalid parameter name: 'sizeof' is a keyword
param static ok FAIL error: invalid storage class specifier in function declarator
param typedef ok FAIL error: invalid storage class specifier in function declarator
param union ok FAIL error: declaration of anonymous union must be a definition
param unsigned ok FAIL error: 'type-name' cannot be signed or unsigned
param volatile ok FAIL error: expected ')'
param const ok FAIL error: expected ')'
param enum FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwEnum ('enum') at line 6:1
param struct FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwStruct ('struct') at line
param switch FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Expected LParen, got Plus ('+') at line 6:19
param while FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwWhile ('while') at line 6
param for FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwFor ('for') at line 6:12
param if FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: Plus ('+') at line 6:15
param else FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwElse ('else') at line 6:1
param return FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwReturn ('return') at line
param break FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwBreak ('break') at line 6
param continue FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwContinue ('continue') at
param void FAIL - Error: Compile error: parse error in fn 'probe' near line 6: Unexpected token in expression: KwVoid ('void') at line 6:1
param _Bool ok FAIL error: cannot combine with previous 'type-name' declaration specifier
param _Alignas ok FAIL error: expected '('
param _Alignof ok FAIL error: invalid parameter name: '_Alignof' is a keyword
param _Atomic ok FAIL error: expected ')'
param _Complex ok FAIL error: '_Complex type-name' is invalid
param _Generic ok FAIL error: invalid parameter name: '_Generic' is a keyword
param _Imaginary ok FAIL error: '_Complex type-name' is invalid
param _Noreturn ok FAIL error: '_Noreturn' can only appear on functions
param _Static_assert ok FAIL error: invalid parameter name: '_Static_assert' is a keyword
param _Thread_local ok FAIL error: '_Thread_local' is only allowed on variable declarations
param bool ok FAIL error: cannot combine with previous 'type-name' declaration specifier
param true ok FAIL error: expected ')'
param false ok FAIL error: expected ')'
param alignas ok ok
param alignof ok ok
param constexpr ok ok
param nullptr ok ok
param static_assert ok FAIL error: invalid parameter name: '_Static_assert' is a keyword
param thread_local ok ok
param typeof ok FAIL error: expected '(' after 'typeof'
param typeof_unqual ok ok
param NULL ok FAIL error: expected ')'
param errno ok ok
param assert ok ok
param main ok ok
param strlen ok ok
param printf ok ok
param abs ok ok
param rest ok ok
Activity
github-actions commentedon Oct 17, 2026
Closed by the issue-shape gate (specs/policy/issue_shape.t27, #7498): an issue must have the template's sections. Edit the body; an edit that passes reopens it.
- specs/x.t27); without it the Queen reserves nothing and no bee can take the issueFR-001: ... MUST ...github-actions commentedon Oct 17, 2026
The shape check passes now (specs/policy/issue_shape.t27); reopened.
skill(issue-shape): how to file an issue the shape gate keeps open (C…