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
C#: a member or local named field inside a property accessor binds to C# 14's field keyword, so the getter silently returns the synthesized backing field (0) instead of the member, and a local named field fails with CS9273 #461
CSharpGenerator.EscapeIdentifier only escapes reserved keywords, and explicitly leaves contextual keywords alone on the grounds that they "are legal names everywhere else" (CSharpGenerator.cs L213-L240, from #441). field is the exception: since C# 14 (the default for net10.0), field inside a property accessor is the keyword for a synthesized backing field. So a reference to a member named field from a getter or setter body changes meaning without an error.
Repro (generated with CSharpGenerator, then built and run with the .NET 10 SDK)
The AST is a class Box with private int field = 42;, plus a property Field whose getter body is return field; and whose setter body is field = value;. There is also a property Twice whose getter declares a local int field = 2; return field;. The generator writes all of these verbatim:
new Box().Field prints 0, not 42. The compiler gives only warning CS9258 ("the 'field' keyword binds to a synthesized backing field ... use 'this.field' or '@field' instead"). The setter likewise writes to the hidden backing field, and the declared member is never read (CS0414).
Twice fails to compile: error CS9273 "'field' is a keyword within a property accessor. Rename the variable or use the identifier '@field' instead."
Every other target reads the declared member, so the C# output computes a different value from the same AST. The test suite would not catch this, because it is a warning and not a build failure.
Suggested fix / acceptance criteria
Write an identifier named field as @field in C# at both the declaration and the reference. The simplest version does this everywhere, since @field is the same identifier wherever it appears. A narrower version does it only inside property accessor bodies. Either way, the escape goes through the existing EscapeIdentifier/EscapeReference hook so declarations and uses agree.
Update the remark on ReservedWords so it no longer says every contextual keyword is safe.
Add a test in CSharpGeneratedSourceCompilesTests covering the class above. It should build with no CS9258/CS9273, and running it should show the getter returning the member's value (42) and the setter updating that member.
Related: #106 (reserved-word escaping in general) and #441 (C# escaping, which deliberately excluded contextual keywords).
Priority: High. The C# output silently computes a different value from the same AST: the getter returns the synthesized backing field (0), not the member (42), and the compiler gives only warning CS9258. The local-variable case fails to build with CS9273. Wrong output with no error is worse than a build failure, and net10.0 uses C# 14 by default, so it applies to every current C# target.
Notes: Escaping field everywhere is the smaller and safer fix, because @field names the same identifier in every context, so declarations and references cannot drift apart. The acceptance test should treat CS9258 as a failure, either by checking for the warning or by asserting the runtime value 42, since the build alone passes.
Activity
matt-edmondson commentedon Nov 6, 2026
Triage
CSharpGenerator.EscapeIdentifier/EscapeReferenceand theReservedWordsremark. Whoever owns the identifier-escaping work in Escape keyword-named identifiers in the C# generator [patch] #441 is the natural owner.fn type(...)(Rust) /int default()(C++) that fails to compile #106 is the umbrella issue for keyword-named identifiers. This issue is the one contextual-keyword exception that Escape keyword-named identifiers in the C# generator [patch] #441 left out on purpose.field.fieldeverywhere is the smaller and safer fix, because@fieldnames the same identifier in every context, so declarations and references cannot drift apart. The acceptance test should treat CS9258 as a failure, either by checking for the warning or by asserting the runtime value 42, since the build alone passes.Generated by Claude Code