You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Identifiers that are target-language keywords are emitted verbatim: a property named Type or Default generates fn type(...) (Rust) / int default() (C++) that fails to compile #106
No generator checks a spelled identifier against the target language's reserved words. LanguageGeneratorBase and the language generators have no reserved-word set, and names only go through NameStyles.Spell (re-casing). The AST is language-neutral, so a name that is perfectly legal in one target becomes a keyword in another once re-cased. The clearest case is the getter name made up for a computed property, PropertyDeclaration.GetterName (Coder/Ast/PropertyDeclaration.cs ~141), used from StandardLanguageGenerator.cs ~393:
Repro (reproduced with MSTest, output checked with the real compilers)
A struct Box with new PropertyDeclaration("Type", "int") and new PropertyDeclaration("Default", "int"), each with a getter body return 1. Both are ordinary C# property names.
Target
Generated
Compiler
Rust
pub fn type(&self) -> i32
rustc: expected identifier, found keyword 'type'
C++
int default() const
g++ -std=c++20: expected unqualified-id before 'default'
C
Box_type
fine, because of the prefix
The same applies to any other spelled identifier (parameter, local, field, function) that lands on a keyword after casing, e.g. match/impl/fn in Rust, range/func/type in Go, lambda/def in Python, delete/new in C++.
This is independent of #71: ktsu.CaseConverter would still produce type.
Suggested fix / acceptance criteria
Give each generator a reserved-word set and a single EscapeIdentifier hook applied wherever a name is spelled:
Rust: raw identifiers (r#type). Note that self/Self/crate/super can't be raw, so suffix those.
Priority: High. Ordinary, idiomatic C# names (Type, Default, Match, Range, Delete, …) produce Rust and C++ output that doesn't compile, and the same gap exists across every target language. For a multi-target code generator this is a systemic correctness hole, not an edge case, because no generator has a reserved-word list at all.
Area / suggested assignee:LanguageGeneratorBase (a new EscapeIdentifier hook and a per-language reserved-word set), NameStyles.Spell call sites, and PropertyDeclaration.GetterName. Owner: @matt-edmondson
Next step: add the escaping at one choke point, wherever a name is spelled, so declaration and use sites can't drift apart. Start with Rust (r#, suffixing self/Self/crate/super) and C++, where the repro fails. Then roll it out to Go, Python, JS, C and C#, with a compile test per target covering a keyword-named property and parameter.
An automated issue sweep picked this up and set it aside before starting, because it is larger than one implement-and-test cycle:
Names are spelled in about 200 places across the seven generators: .Name and *Name( sites in CGenerator (27), CSharpGenerator (25), CppGenerator (20), GoGenerator (41), JavaScriptGenerator (21), PythonGenerator (24), RustGenerator (39) and StandardLanguageGenerator (10). There's no shared choke point today. NameStyles.Spell only covers invented names (GetterName/SetterName), and declared names are written verbatim at each declaration and use site.
An escape has to land at both the declaration and every reference (parameters, locals, fields, calls, member access), or the output stops resolving. Doing that for all targets in one PR is a big, risky diff.
The suffix escape (default_, type_) renames the symbol in C/C++/Go/Python/JS, which the NamingStyle remarks say the AST deliberately never does. Raw identifiers (r#type, @default) don't rename. That trade-off is worth an explicit decision.
Suggested split so each piece is actionable:
Add LanguageGeneratorBase.EscapeIdentifier(string) and a per-language reserved-word set, and route the Rust generator through it (r#, plus a suffix for self/Self/crate/super), with a rustc compile test.
C++ (and C, which shares CFamilyGenerator), with a g++ compile test.
Go, Python, JS and C#, one child issue each.
Left unassigned so the next sweep can pick up a child issue once the split exists.
Status after #330 (commit 9b2e445, "Escape keyword-named identifiers in the Rust and C++ generators"):
Done: steps 1 and 2 of the split above. LanguageGeneratorBase has the escape hook and reserved-word sets, and Rust and C++ go through it.
Still open: step 3. The Go, Python, JavaScript and C# generators still write keyword-named identifiers verbatim. C is still covered only by its Type_ prefix for members, so a keyword-named parameter or local is not escaped.
This issue stays open for those targets. I'm unassigning it so a later sweep can take one target per PR.
Status after #441 ("Escape keyword-named identifiers in the C# generator"):
In review: C#. Keyword names are written as verbatim identifiers (@default), at the declaration and at every use.
Still open: Go and JavaScript still write keyword-named identifiers verbatim. C still escapes only through its Type_ member prefix, so a parameter or local named after a keyword is not escaped.
I'm unassigning this so a later sweep can take one target per PR.
In review, C (Escape keyword-named identifiers in the C generator [patch] #446): struct fields, parameters, locals and free functions get an underscore (auto_, for_). Member functions were already safe as Type_name. Still open in C: the function-pointer member of an interface's vtable struct, (*name).
Still open: JavaScript writes keyword-named identifiers as they are. It needs a design decision first. JavaScript accepts keywords as property names after . (holder.default), so it should escape only bindings: parameters, locals, functions and top-level names. But the shared EscapeReference escapes every segment, and SpellCallee escapes whatever the receiver is. One of them has to become JS-aware, or JS has to accept renaming members it doesn't need to rename.
The two PRs are independent and merge cleanly in either order. I'm unassigning this so a later sweep can take JavaScript.
In review, JavaScript (Escape keyword-named bindings in the JavaScript generator [patch] #462): keyword-named bindings get an underscore where they are declared and wherever they are used: parameters, locals, free functions, classes, and top-level names (in_, return_, import_). Members keep their names, because JavaScript reads holder.default and this.switch() as written. This settles the open design question with a new EscapesMemberNames hook on LanguageGeneratorBase: EscapeReference escapes only the leading segment, and SpellCallee counts only free functions. Every other target keeps today's behaviour.
Activity
matt-edmondson commentedon Sep 27, 2026
Triage
Type,Default,Match,Range,Delete, …) produce Rust and C++ output that doesn't compile, and the same gap exists across every target language. For a multi-target code generator this is a systemic correctness hole, not an edge case, because no generator has a reserved-word list at all.LanguageGeneratorBase(a newEscapeIdentifierhook and a per-language reserved-word set),NameStyles.Spellcall sites, andPropertyDeclaration.GetterName. Owner: @matt-edmondsonr#, suffixingself/Self/crate/super) and C++, where the repro fails. Then roll it out to Go, Python, JS, C and C#, with a compile test per target covering a keyword-named property and parameter.Generated by Claude Code
matt-edmondson commentedon Oct 8, 2026
Triage
An automated issue sweep picked this up and set it aside before starting, because it is larger than one implement-and-test cycle:
.Nameand*Name(sites inCGenerator(27),CSharpGenerator(25),CppGenerator(20),GoGenerator(41),JavaScriptGenerator(21),PythonGenerator(24),RustGenerator(39) andStandardLanguageGenerator(10). There's no shared choke point today.NameStyles.Spellonly covers invented names (GetterName/SetterName), and declared names are written verbatim at each declaration and use site.default_,type_) renames the symbol in C/C++/Go/Python/JS, which theNamingStyleremarks say the AST deliberately never does. Raw identifiers (r#type,@default) don't rename. That trade-off is worth an explicit decision.Suggested split so each piece is actionable:
LanguageGeneratorBase.EscapeIdentifier(string)and a per-language reserved-word set, and route the Rust generator through it (r#, plus a suffix forself/Self/crate/super), with a rustc compile test.CFamilyGenerator), with a g++ compile test.Left unassigned so the next sweep can pick up a child issue once the split exists.
Generated by Claude Code
matt-edmondson commentedon Oct 29, 2026
Triage
Status after #330 (commit 9b2e445, "Escape keyword-named identifiers in the Rust and C++ generators"):
LanguageGeneratorBasehas the escape hook and reserved-word sets, and Rust and C++ go through it.Type_prefix for members, so a keyword-named parameter or local is not escaped.This issue stays open for those targets. I'm unassigning it so a later sweep can take one target per PR.
Generated by Claude Code
Escape keyword-named identifiers in the Python generator [patch]
matt-edmondson commentedon Nov 3, 2026
Triage
Status after #441 ("Escape keyword-named identifiers in the C# generator"):
@default), at the declaration and at every use.Type_member prefix, so a parameter or local named after a keyword is not escaped.I'm unassigning this so a later sweep can take one target per PR.
Generated by Claude Code
Escape keyword-named identifiers in the C# generator [patch] (#441)
matt-edmondson commentedon Nov 4, 2026
Triage
Status after #445 (Go) and #446 (C):
type_,chan_) where they are declared and wherever they are used. Predeclared identifiers (nil,true,len) are not escaped.auto_,for_). Member functions were already safe asType_name. Still open in C: the function-pointer member of an interface's vtable struct,(*name)..(holder.default), so it should escape only bindings: parameters, locals, functions and top-level names. But the sharedEscapeReferenceescapes every segment, andSpellCalleeescapes whatever the receiver is. One of them has to become JS-aware, or JS has to accept renaming members it doesn't need to rename.The two PRs are independent and merge cleanly in either order. I'm unassigning this so a later sweep can take JavaScript.
Generated by Claude Code
Escape keyword-named bindings in the JavaScript generator [patch]
matt-edmondson commentedon Nov 7, 2026
Triage
Status after #462 (JavaScript):
in_,return_,import_). Members keep their names, because JavaScript readsholder.defaultandthis.switch()as written. This settles the open design question with a newEscapesMemberNameshook onLanguageGeneratorBase:EscapeReferenceescapes only the leading segment, andSpellCalleecounts only free functions. Every other target keeps today's behaviour.(*name).Unassigning this so a later sweep can take the C vtable piece.
Generated by Claude Code