Skip to content

Commit e4aadd4

Browse files
Joshua GriffithJoshua Griffith
authored andcommitted
Use PascalCase for record property names (#152)
Record positional parameters now use PascalCase per C# naming conventions instead of @lowerCamelCase. Keywords are prefixed with @ only when necessary. - Add property_name filter for record/enum variant fields - Update RecordTemplate.cs to use property_name - Update EnumTemplate.cs to use property_name for variant fields - Update destroy_fields macro to use property_name - Add regression test fixture for issue-152 Signed-off-by: Joshua Griffith <Joshua.Griffith@fnf.com>
1 parent 5c7dbe2 commit e4aadd4

File tree

21 files changed

+252
-83
lines changed

21 files changed

+252
-83
lines changed

bindgen/src/gen_cs/filters.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ pub(super) fn var_name(nm: impl AsRef<str>) -> Result<String, askama::Error> {
122122
Ok(oracle().var_name(nm.as_ref()))
123123
}
124124

125+
/// Get the idiomatic C# rendering of a property name (for record positional parameters).
126+
pub(super) fn property_name(nm: impl AsRef<str>) -> Result<String, askama::Error> {
127+
Ok(oracle().property_name(nm.as_ref()))
128+
}
129+
125130
/// Get the idiomatic C# rendering of an individual enum variant.
126131
pub(super) fn enum_variant(nm: &str) -> Result<String, askama::Error> {
127132
Ok(oracle().enum_variant_name(nm))

bindgen/src/gen_cs/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,36 @@ impl CsCodeOracle {
335335
format!("@{}", nm.to_string().to_lower_camel_case())
336336
}
337337

338+
/// Get the idiomatic C# rendering of a property name (for record positional parameters).
339+
/// Uses PascalCase per Microsoft naming conventions. Prefixes with @ only for C# keywords.
340+
fn property_name(&self, nm: &str) -> String {
341+
let name = nm.to_string().to_upper_camel_case();
342+
if Self::is_csharp_keyword(&name) {
343+
format!("@{}", name)
344+
} else {
345+
name
346+
}
347+
}
348+
349+
/// Check if a name is a C# keyword that requires an @ prefix.
350+
fn is_csharp_keyword(name: &str) -> bool {
351+
matches!(
352+
name,
353+
// C# reserved keywords (case-sensitive)
354+
"Abstract" | "As" | "Base" | "Bool" | "Break" | "Byte" | "Case" | "Catch"
355+
| "Char" | "Checked" | "Class" | "Const" | "Continue" | "Decimal" | "Default"
356+
| "Delegate" | "Do" | "Double" | "Else" | "Enum" | "Event" | "Explicit"
357+
| "Extern" | "False" | "Finally" | "Fixed" | "Float" | "For" | "Foreach"
358+
| "Goto" | "If" | "Implicit" | "In" | "Int" | "Interface" | "Internal" | "Is"
359+
| "Lock" | "Long" | "Namespace" | "New" | "Null" | "Object" | "Operator"
360+
| "Out" | "Override" | "Params" | "Private" | "Protected" | "Public"
361+
| "Readonly" | "Ref" | "Return" | "Sbyte" | "Sealed" | "Short" | "Sizeof"
362+
| "Stackalloc" | "Static" | "String" | "Struct" | "Switch" | "This" | "Throw"
363+
| "True" | "Try" | "Typeof" | "Uint" | "Ulong" | "Unchecked" | "Unsafe"
364+
| "Ushort" | "Using" | "Virtual" | "Void" | "Volatile" | "While"
365+
)
366+
}
367+
338368
/// Get the idiomatic C# rendering of an individual enum variant.
339369
fn enum_variant_name(&self, nm: &str) -> String {
340370
nm.to_string().to_upper_camel_case()

bindgen/templates/EnumTemplate.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public record {{ variant.name()|class_name(ci) }}: {{ type_name }} {}
4949
{% else -%}
5050
public record {{ variant.name()|class_name(ci) }} (
5151
{%- for field in variant.fields() %}
52-
{%- let field_name = field.name()|or_pos_var(loop.index)|var_name %}
52+
{%- let field_name = field.name()|or_pos_var(loop.index)|property_name %}
5353
{% call cs::enum_parameter_type_name(field|type_name(ci), variant.name()|class_name(ci)) %} {{ field_name }}{% if !loop.last %},{% endif %}
5454
{%- endfor %}
5555
) : {{ type_name }} {}
@@ -98,7 +98,7 @@ public override int AllocationSize({{ type_name }} value) {
9898
case {{ type_name }}.{{ variant.name()|class_name(ci) }} variant_value:
9999
return 4
100100
{%- for field in variant.fields() %}
101-
{%- let field_name = field.name()|or_pos_var(loop.index)|var_name %}
101+
{%- let field_name = field.name()|or_pos_var(loop.index)|property_name %}
102102
+ {{ field|allocation_size_fn }}(variant_value.{{ field_name }})
103103
{%- endfor %};
104104
{%- endfor %}
@@ -113,7 +113,7 @@ public override void Write({{ type_name }} value, BigEndianStream stream) {
113113
case {{ type_name }}.{{ variant.name()|class_name(ci) }} variant_value:
114114
stream.WriteInt({{ loop.index }});
115115
{%- for field in variant.fields() %}
116-
{%- let field_name = field.name()|or_pos_var(loop.index)|var_name %}
116+
{%- let field_name = field.name()|or_pos_var(loop.index)|property_name %}
117117
{{ field|write_fn }}(variant_value.{{ field_name }}, stream);
118118
{%- endfor %}
119119
break;

bindgen/templates/RecordTemplate.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
{{ config.access_modifier() }} record {{ type_name }} (
3030
{%- for field in ordered_fields %}
3131
{%- call cs::docstring(field, 4) %}
32-
{{ field|type_name(ci) }} {{ field.name()|var_name -}}
32+
{{ field|type_name(ci) }} {{ field.name()|property_name -}}
3333
{%- match field.default_value() %}
3434
{%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }}
3535
{%- else %}
@@ -50,21 +50,21 @@ class {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
5050
public override {{ type_name }} Read(BigEndianStream stream) {
5151
return new {{ type_name }}(
5252
{%- for field in rec.fields() %}
53-
{{ field.name()|var_name }}: {{ field|read_fn }}(stream){% if !loop.last %},{% endif%}
53+
{{ field.name()|property_name }}: {{ field|read_fn }}(stream){% if !loop.last %},{% endif%}
5454
{%- endfor %}
5555
);
5656
}
5757

5858
public override int AllocationSize({{ type_name }} value) {
5959
return 0
6060
{%- for field in rec.fields() %}
61-
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
61+
+ {{ field|allocation_size_fn }}(value.{{ field.name()|property_name }})
6262
{%- endfor -%};
6363
}
6464

6565
public override void Write({{ type_name }} value, BigEndianStream stream) {
6666
{%- for field in rec.fields() %}
67-
{{ field|write_fn }}(value.{{ field.name()|var_name }}, stream);
67+
{{ field|write_fn }}(value.{{ field.name()|property_name }}, stream);
6868
{%- endfor %}
6969
}
7070
}

bindgen/templates/macros.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@
124124
{%- macro destroy_fields(member, prefix) %}
125125
FFIObjectUtil.DisposeAll(
126126
{%- for field in member.fields() %}
127-
{{ prefix }}.{{ field.name()|var_name }}{% if !loop.last %},{% endif %}
127+
{{ prefix }}.{{ field.name()|property_name }}{% if !loop.last %},{% endif %}
128128
{%- endfor %});
129129
{%- endmacro -%}
130130

dotnet-tests/UniffiCS.BindingTests/OptionalParameterTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ public class OptionalParameterTests
1212
[Fact]
1313
public void OptionalParameter_CanBeOmitted()
1414
{
15-
var person = new Person(isSpecial: false);
15+
var person = new Person(IsSpecial: false);
1616
string message = Hello(person);
1717
Assert.Equal("Hello stranger!", message);
1818
}
1919

2020
[Fact]
2121
public void OptionalParameter_CanBeSpecified()
2222
{
23-
var person = new Person(name: "John Connor", isSpecial: false);
23+
var person = new Person(Name: "John Connor", IsSpecial: false);
2424
string message = Hello(person);
2525
Assert.Equal("Hello John Connor!", message);
2626
}

dotnet-tests/UniffiCS.BindingTests/TestCoverall.cs

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,27 +31,27 @@ public void TestCreateSomeDict()
3131
{
3232
using (var d = CoverallMethods.CreateSomeDict())
3333
{
34-
Assert.Equal("text", d.text);
35-
Assert.Equal("maybe_text", d.maybeText);
36-
Assert.True(d.aBool);
37-
Assert.False(d.maybeABool);
38-
Assert.Equal((byte)1, d.unsigned8);
39-
Assert.Equal((byte)2, d.maybeUnsigned8);
40-
Assert.Equal((ushort)3, d.unsigned16);
41-
Assert.Equal((ushort)4, d.maybeUnsigned16);
42-
Assert.Equal(18446744073709551615UL, d.unsigned64);
43-
Assert.Equal(0ul, d.maybeUnsigned64);
44-
Assert.Equal((sbyte)8, d.signed8);
45-
Assert.Equal((sbyte)0, d.maybeSigned8);
46-
Assert.Equal(9223372036854775807L, d.signed64);
47-
Assert.Equal(0L, d.maybeSigned64);
48-
49-
Assert.Equal(1.2345f, d.float32);
50-
Assert.Equal(22.0f / 7.0f, d.maybeFloat32);
51-
Assert.Equal(0.0, d.float64);
52-
Assert.Equal(1.0, d.maybeFloat64);
53-
54-
Assert.Equal("some_dict", d.coveralls!.GetName());
34+
Assert.Equal("text", d.Text);
35+
Assert.Equal("maybe_text", d.MaybeText);
36+
Assert.True(d.ABool);
37+
Assert.False(d.MaybeABool);
38+
Assert.Equal((byte)1, d.Unsigned8);
39+
Assert.Equal((byte)2, d.MaybeUnsigned8);
40+
Assert.Equal((ushort)3, d.Unsigned16);
41+
Assert.Equal((ushort)4, d.MaybeUnsigned16);
42+
Assert.Equal(18446744073709551615UL, d.Unsigned64);
43+
Assert.Equal(0ul, d.MaybeUnsigned64);
44+
Assert.Equal((sbyte)8, d.Signed8);
45+
Assert.Equal((sbyte)0, d.MaybeSigned8);
46+
Assert.Equal(9223372036854775807L, d.Signed64);
47+
Assert.Equal(0L, d.MaybeSigned64);
48+
49+
Assert.Equal(1.2345f, d.Float32);
50+
Assert.Equal(22.0f / 7.0f, d.MaybeFloat32);
51+
Assert.Equal(0.0, d.Float64);
52+
Assert.Equal(1.0, d.MaybeFloat64);
53+
54+
Assert.Equal("some_dict", d.Coveralls!.GetName());
5555
}
5656
}
5757

@@ -162,7 +162,7 @@ public void TestErrorValues()
162162

163163
var complexError = CoverallMethods.GetComplexError(null);
164164
Assert.True(complexError is ComplexException.PermissionDenied);
165-
Assert.Null(CoverallMethods.GetErrorDict(null).complexError);
165+
Assert.Null(CoverallMethods.GetErrorDict(null).ComplexError);
166166
}
167167
}
168168

@@ -378,14 +378,14 @@ public void TestStringUtil()
378378
public void TestDictWithDefaults()
379379
{
380380
var d = new DictWithDefaults();
381-
Assert.Equal("default-value", d.name);
382-
Assert.Equal(31UL, d.integer);
383-
Assert.Null(d.category);
381+
Assert.Equal("default-value", d.Name);
382+
Assert.Equal(31UL, d.Integer);
383+
Assert.Null(d.Category);
384384

385385
var d1 = new DictWithDefaults("this", "that", 42UL);
386-
Assert.Equal("this", d1.name);
387-
Assert.Equal("that", d1.category);
388-
Assert.Equal(42UL, d1.integer);
386+
Assert.Equal("this", d1.Name);
387+
Assert.Equal("that", d1.Category);
388+
Assert.Equal(42UL, d1.Integer);
389389
}
390390

391391
[Fact]

dotnet-tests/UniffiCS.BindingTests/TestCustomTypes.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ public void CustomTypesWork()
1414
{
1515
var demo = CustomTypesMethods.GetCustomTypesDemo(null);
1616

17-
Assert.Equal("http://example.com/", demo.url);
18-
Assert.Equal(123, demo.handle);
17+
Assert.Equal("http://example.com/", demo.Url);
18+
Assert.Equal(123, demo.Handle);
1919

2020
// Change some data and ensure that the round-trip works
2121
demo = demo with
2222
{
23-
url = "http://new.example.com/"
23+
Url = "http://new.example.com/"
2424
};
25-
demo = demo with { handle = 456 };
25+
demo = demo with { Handle = 456 };
2626
Assert.Equal(demo, CustomTypesMethods.GetCustomTypesDemo(demo));
2727
}
2828
}

dotnet-tests/UniffiCS.BindingTests/TestCustomTypesBuiltin.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ public void CustomTypesWork()
2222

2323
void AssertDemo(CustomTypesBuiltin demo)
2424
{
25-
Assert.Equal("Hello, world!", demo.@string);
26-
Assert.Equal(new List<String> { "Hello, world!" }, demo.array);
27-
Assert.Equal(new Dictionary<String, String> { { "hello", "world" } }, demo.table);
28-
Assert.True(demo.boolean);
29-
Assert.Equal(SByte.MaxValue, demo.int8);
30-
Assert.Equal(Int16.MaxValue, demo.int16);
31-
Assert.Equal(Int32.MaxValue, demo.int32);
32-
Assert.Equal(Int64.MaxValue, demo.int64);
33-
Assert.Equal(Byte.MaxValue, demo.uint8);
34-
Assert.Equal(UInt16.MaxValue, demo.uint16);
35-
Assert.Equal(UInt32.MaxValue, demo.uint32);
36-
Assert.Equal(UInt64.MaxValue, demo.uint64);
37-
Assert.Equal(Single.MaxValue, demo.@float);
38-
Assert.Equal(Double.MaxValue, demo.@double);
25+
Assert.Equal("Hello, world!", demo.@String);
26+
Assert.Equal(new List<String> { "Hello, world!" }, demo.Array);
27+
Assert.Equal(new Dictionary<String, String> { { "hello", "world" } }, demo.Table);
28+
Assert.True(demo.Boolean);
29+
Assert.Equal(SByte.MaxValue, demo.Int8);
30+
Assert.Equal(Int16.MaxValue, demo.Int16);
31+
Assert.Equal(Int32.MaxValue, demo.Int32);
32+
Assert.Equal(Int64.MaxValue, demo.Int64);
33+
Assert.Equal(Byte.MaxValue, demo.Uint8);
34+
Assert.Equal(UInt16.MaxValue, demo.Uint16);
35+
Assert.Equal(UInt32.MaxValue, demo.Uint32);
36+
Assert.Equal(UInt64.MaxValue, demo.Uint64);
37+
Assert.Equal(Single.MaxValue, demo.@Float);
38+
Assert.Equal(Double.MaxValue, demo.@Double);
3939
}
4040
}

dotnet-tests/UniffiCS.BindingTests/TestDocstring.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void DocstringWorks()
4343
obj2.Test();
4444

4545
var record = new RecordTest(123);
46-
_ = record.test;
46+
_ = record.Test;
4747

4848
CallbackTest callback = new CallbackImpls();
4949
callback.Test();

0 commit comments

Comments
 (0)