Skip to content

t27c gen-c: identifiers named for C keywords are emitted verbatim, so the C does not compile (short, int, signed, default, ...) #10054

Closed

Description

@gHashTag

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 runs t27c gen-c and then zig cc --target=wasm32-wasi -c on the output, then the C compiles, and the test block still asserts probe(1) == 2.
  • Given a spec with a parameter named for a C keyword (fn f(signed: bool), as in specs/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-c MUST 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 keyword typeof.
  • 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.rs MUST carry the owner's owner-approved-foreign label 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 a t27_ prefix for any name in the list above), applied at every place CCodegen writes a user identifier: locals, parameters, fn names, struct fields, consts and enum members. Uses and declarations must go through the same function, as zig_value_ident does (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, CCodegen itself 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-c on every probe above produces C that compiles under zig 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

  1. added
    shape-refusedClosed by the issue-shape gate (specs/policy/issue_shape.t27); a passing edit reopens it
    on Oct 17, 2026
  2. github-actions commented on Oct 17, 2026

    @github-actions
    Contributor

    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.

    • missing: ## Boundary -- list every file this task may touch, one per line (- specs/x.t27); without it the Queen reserves nothing and no bee can take the issue
    • missing: ## User Scenarios -- at least one Given/When/Then scenario
    • missing: ## Requirements -- numbered obligations, FR-001: ... MUST ...
  3. removed
    shape-refusedClosed by the issue-shape gate (specs/policy/issue_shape.t27); a passing edit reopens it
    on Oct 17, 2026
  4. github-actions commented on Oct 17, 2026

    @github-actions
    Contributor

    The shape check passes now (specs/policy/issue_shape.t27); reopened.

  5. added a commit that references this issue on Oct 17, 2026
    063da04
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions