Skip to content

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

Open

Description

@matt-edmondson

What's wrong

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.
    • C++/C/Go/Python/JS: append _ (e.g. default_, type_).
    • C#: @default.
  • Apply it consistently at declaration and use sites so references still resolve.
  • Add compile tests with a property named Type/Default and a parameter named after a keyword in each target.

Activity

  1. matt-edmondson commented on Sep 27, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    • Category: Bug
    • 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
    • Duplicates / in progress: not a duplicate. As the issue notes, it is independent of Delegate naming-style spelling to ktsu.CaseConverter #71 (CaseConverter adoption). No open PR in Coder covers it.
    • 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.

    Generated by Claude Code

  2. matt-edmondson commented on Oct 8, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    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:

    1. 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.
    2. C++ (and C, which shares CFamilyGenerator), with a g++ compile test.
    3. 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.


    Generated by Claude Code

  3. matt-edmondson commented on Oct 29, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    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.


    Generated by Claude Code

  4. matt-edmondson commented on Nov 3, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    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.


    Generated by Claude Code

  5. removed their assignment
    on Nov 3, 2026
  6. matt-edmondson commented on Nov 4, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Status after #445 (Go) and #446 (C):

    • In review, Go (Escape keyword-named identifiers in the Go generator [patch] #445): keyword names get an underscore (type_, chan_) where they are declared and wherever they are used. Predeclared identifiers (nil, true, len) are not escaped.
    • 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.


    Generated by Claude Code

  7. matt-edmondson commented on Nov 7, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Status after #462 (JavaScript):

    Unassigning this so a later sweep can take the C vtable piece.


    Generated by Claude Code

  8. removed their assignment
    on Nov 7, 2026
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

    bugSomething isn't workingreadyFully specified; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions