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.
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