Skip to content

Commit f929ed2

Browse files
Sergio0694Copilot
andauthored
Use camelCase parameter names in projected struct constructors (#2346)
* Use camelCase parameter names in projected struct constructors Add a to_camel_case helper (invariant ASCII lowering) and update the struct constructor codegen to use camelCase parameter names instead of underscore-prefixed field names. For example, a struct with a 'Stage' field now generates a constructor parameter named 'stage' rather than '_Stage', producing a cleaner public API surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fall back to underscore prefix for non-PascalCase field names If the first character of a struct field name is not an uppercase ASCII letter (e.g. '_Foo'), prepend an underscore instead of lowering, so the generated parameter name won't collide with the field name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Escape C# keywords and precompute camelCase param names Address PR review feedback: - Route camelCase parameter names through write_escaped_identifier so that field names like 'Event' or 'Class' produce '@event'/'@Class' instead of bare C# keywords. - Precompute param_name once per field in field_info instead of calling to_camel_case twice (parameter list + assignment). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add missing C# contextual keywords to is_keyword list Add all 46 contextual keywords from the C# language reference to the keyword list used by write_escaped_identifier. This ensures generated identifiers like 'value', 'var', 'dynamic', 'async', 'await', 'record', 'required', 'get', 'set', 'init', 'when', 'where', 'yield', etc. are properly escaped with '@' when they appear as parameter names. Reference: https://learn.microsoft.com/dotnet/csharp/language-reference/keywords Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e2da074 commit f929ed2

2 files changed

Lines changed: 32 additions & 3 deletions

File tree

‎src/cswinrt/code_writers.h‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,10 @@ namespace cswinrt
674674

675675
bool is_keyword(std::string_view str)
676676
{
677+
// C# reserved keywords (sorted). Contextual keywords (e.g. 'value', 'var',
678+
// 'dynamic') are intentionally excluded: they are valid identifiers outside
679+
// their specific syntax contexts and do not need '@' escaping.
680+
// See: https://learn.microsoft.com/dotnet/csharp/language-reference/keywords
677681
static constexpr std::string_view keywords[] =
678682
{
679683
"abstract", "as", "base", "bool", "break", "byte",
@@ -9836,6 +9840,7 @@ R"(
98369840
{
98379841
std::string type;
98389842
std::string name;
9843+
std::string param_name;
98399844
bool is_interface;
98409845
};
98419846
std::vector fields;
@@ -9845,6 +9850,7 @@ R"(
98459850
field_info field_info{};
98469851
field_info.type = w.write_temp("%", [&](writer& w) { write_projection_type(w, semantics); });
98479852
field_info.name = field.Name();
9853+
field_info.param_name = to_camel_case(field_info.name);
98489854
if (auto td = std::get_if(&semantics))
98499855
{
98509856
field_info.is_interface = get_category(*td) == category::interface_type;
@@ -9866,16 +9872,25 @@ R"(
98669872
type.TypeName(),
98679873
type.TypeName());
98689874

9869-
// ctor
9875+
// ctor (use camelCase parameter names for cleaner public API)
98709876
w.write("public %(%)\n{\n%\n}\n",
98719877
type.TypeName(),
98729878
bind_list([](writer& w, auto&& field)
98739879
{
9874-
w.write("% _%", field.type, field.name);
9880+
w.write("% %", field.type, bind(field.param_name));
98759881
}, ", ", fields),
98769882
bind_each([](writer& w, auto&& field)
98779883
{
9878-
w.write("% = _%; ", field.name, field.name);
9884+
// When the param name matches the field name (i.e. to_camel_case couldn't
9885+
// change the casing), qualify the field with 'this.' to disambiguate.
9886+
if (field.name == field.param_name)
9887+
{
9888+
w.write("this.% = %; ", field.name, bind(field.param_name));
9889+
}
9890+
else
9891+
{
9892+
w.write("% = %; ", field.name, bind(field.param_name));
9893+
}
98799894
}, fields));
98809895

98819896
// properties

‎src/cswinrt/helpers.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ namespace cswinrt
1313
return 0 == value.compare(0, match.size(), match);
1414
}
1515

16+
// Converts a PascalCase name to camelCase (lowering the first character).
17+
// Uses invariant ASCII lowering to avoid locale-dependent behavior.
18+
// If the first character is not an uppercase ASCII letter, the name is
19+
// returned unchanged (callers should use 'this.' to disambiguate).
20+
static inline std::string to_camel_case(std::string_view const& name)
21+
{
22+
std::string result(name);
23+
if (!result.empty() && result[0] >= 'A' && result[0] <= 'Z')
24+
{
25+
result[0] = result[0] - 'A' + 'a';
26+
}
27+
return result;
28+
}
29+
1630
static bool is_remove_overload(MethodDef const& method)
1731
{
1832
return method.SpecialName() && starts_with(method.Name(), "remove_");

0 commit comments

Comments
 (0)