Commit f265fb81 authored by Jon Skeet's avatar Jon Skeet

Merge pull request #1401 from jskeet/enum-casing

Enum casing in C#
parents 36978c39 d90d615f
......@@ -154,6 +154,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs \
csharp/src/Google.Protobuf/Reflection/OneofAccessor.cs \
csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs \
csharp/src/Google.Protobuf/Reflection/OriginalNameAttribute.cs \
csharp/src/Google.Protobuf/Reflection/PackageDescriptor.cs \
csharp/src/Google.Protobuf/Reflection/PartialClasses.cs \
csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs \
......
......@@ -73,13 +73,13 @@ namespace Google.Protobuf.Examples.AddressBook
switch (type)
{
case "mobile":
phoneNumber.Type = Person.Types.PhoneType.MOBILE;
phoneNumber.Type = Person.Types.PhoneType.Mobile;
break;
case "home":
phoneNumber.Type = Person.Types.PhoneType.HOME;
phoneNumber.Type = Person.Types.PhoneType.Home;
break;
case "work":
phoneNumber.Type = Person.Types.PhoneType.WORK;
phoneNumber.Type = Person.Types.PhoneType.Work;
break;
default:
output.Write("Unknown phone type. Using default.");
......
......@@ -228,9 +228,9 @@ namespace Google.Protobuf.Examples.AddressBook {
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial class Types {
public enum PhoneType {
MOBILE = 0,
HOME = 1,
WORK = 2,
[pbr::OriginalName("MOBILE")] Mobile = 0,
[pbr::OriginalName("HOME")] Home = 1,
[pbr::OriginalName("WORK")] Work = 2,
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
......@@ -273,7 +273,7 @@ namespace Google.Protobuf.Examples.AddressBook {
/// <summary>Field number for the "type" field.</summary>
public const int TypeFieldNumber = 2;
private global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType type_ = global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType.MOBILE;
private global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType type_ = 0;
public global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType Type {
get { return type_; }
set {
......@@ -300,7 +300,7 @@ namespace Google.Protobuf.Examples.AddressBook {
public override int GetHashCode() {
int hash = 1;
if (Number.Length != 0) hash ^= Number.GetHashCode();
if (Type != global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType.MOBILE) hash ^= Type.GetHashCode();
if (Type != 0) hash ^= Type.GetHashCode();
return hash;
}
......@@ -313,7 +313,7 @@ namespace Google.Protobuf.Examples.AddressBook {
output.WriteRawTag(10);
output.WriteString(Number);
}
if (Type != global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType.MOBILE) {
if (Type != 0) {
output.WriteRawTag(16);
output.WriteEnum((int) Type);
}
......@@ -324,7 +324,7 @@ namespace Google.Protobuf.Examples.AddressBook {
if (Number.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Number);
}
if (Type != global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType.MOBILE) {
if (Type != 0) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Type);
}
return size;
......@@ -337,7 +337,7 @@ namespace Google.Protobuf.Examples.AddressBook {
if (other.Number.Length != 0) {
Number = other.Number;
}
if (other.Type != global::Google.Protobuf.Examples.AddressBook.Person.Types.PhoneType.MOBILE) {
if (other.Type != 0) {
Type = other.Type;
}
}
......
......@@ -55,13 +55,13 @@ namespace Google.Protobuf.Examples.AddressBook
{
switch (phoneNumber.Type)
{
case Person.Types.PhoneType.MOBILE:
case Person.Types.PhoneType.Mobile:
Console.Write(" Mobile phone #: ");
break;
case Person.Types.PhoneType.HOME:
case Person.Types.PhoneType.Home:
Console.Write(" Home phone #: ");
break;
case Person.Types.PhoneType.WORK:
case Person.Types.PhoneType.Work:
Console.Write(" Work phone #: ");
break;
}
......
......@@ -199,15 +199,15 @@ namespace Conformance {
}
#region Enums
public enum WireFormat {
UNSPECIFIED = 0,
PROTOBUF = 1,
JSON = 2,
[pbr::OriginalName("UNSPECIFIED")] Unspecified = 0,
[pbr::OriginalName("PROTOBUF")] Protobuf = 1,
[pbr::OriginalName("JSON")] Json = 2,
}
public enum ForeignEnum {
FOREIGN_FOO = 0,
FOREIGN_BAR = 1,
FOREIGN_BAZ = 2,
[pbr::OriginalName("FOREIGN_FOO")] ForeignFoo = 0,
[pbr::OriginalName("FOREIGN_BAR")] ForeignBar = 1,
[pbr::OriginalName("FOREIGN_BAZ")] ForeignBaz = 2,
}
#endregion
......@@ -278,7 +278,7 @@ namespace Conformance {
/// <summary>Field number for the "requested_output_format" field.</summary>
public const int RequestedOutputFormatFieldNumber = 3;
private global::Conformance.WireFormat requestedOutputFormat_ = global::Conformance.WireFormat.UNSPECIFIED;
private global::Conformance.WireFormat requestedOutputFormat_ = 0;
/// <summary>
/// Which format should the testee serialize its message to?
/// </summary>
......@@ -328,7 +328,7 @@ namespace Conformance {
int hash = 1;
if (payloadCase_ == PayloadOneofCase.ProtobufPayload) hash ^= ProtobufPayload.GetHashCode();
if (payloadCase_ == PayloadOneofCase.JsonPayload) hash ^= JsonPayload.GetHashCode();
if (RequestedOutputFormat != global::Conformance.WireFormat.UNSPECIFIED) hash ^= RequestedOutputFormat.GetHashCode();
if (RequestedOutputFormat != 0) hash ^= RequestedOutputFormat.GetHashCode();
hash ^= (int) payloadCase_;
return hash;
}
......@@ -346,7 +346,7 @@ namespace Conformance {
output.WriteRawTag(18);
output.WriteString(JsonPayload);
}
if (RequestedOutputFormat != global::Conformance.WireFormat.UNSPECIFIED) {
if (RequestedOutputFormat != 0) {
output.WriteRawTag(24);
output.WriteEnum((int) RequestedOutputFormat);
}
......@@ -360,7 +360,7 @@ namespace Conformance {
if (payloadCase_ == PayloadOneofCase.JsonPayload) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(JsonPayload);
}
if (RequestedOutputFormat != global::Conformance.WireFormat.UNSPECIFIED) {
if (RequestedOutputFormat != 0) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) RequestedOutputFormat);
}
return size;
......@@ -370,7 +370,7 @@ namespace Conformance {
if (other == null) {
return;
}
if (other.RequestedOutputFormat != global::Conformance.WireFormat.UNSPECIFIED) {
if (other.RequestedOutputFormat != 0) {
RequestedOutputFormat = other.RequestedOutputFormat;
}
switch (other.PayloadCase) {
......@@ -1044,7 +1044,7 @@ namespace Conformance {
/// <summary>Field number for the "optional_nested_enum" field.</summary>
public const int OptionalNestedEnumFieldNumber = 21;
private global::Conformance.TestAllTypes.Types.NestedEnum optionalNestedEnum_ = global::Conformance.TestAllTypes.Types.NestedEnum.FOO;
private global::Conformance.TestAllTypes.Types.NestedEnum optionalNestedEnum_ = 0;
public global::Conformance.TestAllTypes.Types.NestedEnum OptionalNestedEnum {
get { return optionalNestedEnum_; }
set {
......@@ -1054,7 +1054,7 @@ namespace Conformance {
/// <summary>Field number for the "optional_foreign_enum" field.</summary>
public const int OptionalForeignEnumFieldNumber = 22;
private global::Conformance.ForeignEnum optionalForeignEnum_ = global::Conformance.ForeignEnum.FOREIGN_FOO;
private global::Conformance.ForeignEnum optionalForeignEnum_ = 0;
public global::Conformance.ForeignEnum OptionalForeignEnum {
get { return optionalForeignEnum_; }
set {
......@@ -2079,8 +2079,8 @@ namespace Conformance {
if (OptionalBytes.Length != 0) hash ^= OptionalBytes.GetHashCode();
if (optionalNestedMessage_ != null) hash ^= OptionalNestedMessage.GetHashCode();
if (optionalForeignMessage_ != null) hash ^= OptionalForeignMessage.GetHashCode();
if (OptionalNestedEnum != global::Conformance.TestAllTypes.Types.NestedEnum.FOO) hash ^= OptionalNestedEnum.GetHashCode();
if (OptionalForeignEnum != global::Conformance.ForeignEnum.FOREIGN_FOO) hash ^= OptionalForeignEnum.GetHashCode();
if (OptionalNestedEnum != 0) hash ^= OptionalNestedEnum.GetHashCode();
if (OptionalForeignEnum != 0) hash ^= OptionalForeignEnum.GetHashCode();
if (OptionalStringPiece.Length != 0) hash ^= OptionalStringPiece.GetHashCode();
if (OptionalCord.Length != 0) hash ^= OptionalCord.GetHashCode();
if (recursiveMessage_ != null) hash ^= RecursiveMessage.GetHashCode();
......@@ -2247,11 +2247,11 @@ namespace Conformance {
output.WriteRawTag(154, 1);
output.WriteMessage(OptionalForeignMessage);
}
if (OptionalNestedEnum != global::Conformance.TestAllTypes.Types.NestedEnum.FOO) {
if (OptionalNestedEnum != 0) {
output.WriteRawTag(168, 1);
output.WriteEnum((int) OptionalNestedEnum);
}
if (OptionalForeignEnum != global::Conformance.ForeignEnum.FOREIGN_FOO) {
if (OptionalForeignEnum != 0) {
output.WriteRawTag(176, 1);
output.WriteEnum((int) OptionalForeignEnum);
}
......@@ -2492,10 +2492,10 @@ namespace Conformance {
if (optionalForeignMessage_ != null) {
size += 2 + pb::CodedOutputStream.ComputeMessageSize(OptionalForeignMessage);
}
if (OptionalNestedEnum != global::Conformance.TestAllTypes.Types.NestedEnum.FOO) {
if (OptionalNestedEnum != 0) {
size += 2 + pb::CodedOutputStream.ComputeEnumSize((int) OptionalNestedEnum);
}
if (OptionalForeignEnum != global::Conformance.ForeignEnum.FOREIGN_FOO) {
if (OptionalForeignEnum != 0) {
size += 2 + pb::CodedOutputStream.ComputeEnumSize((int) OptionalForeignEnum);
}
if (OptionalStringPiece.Length != 0) {
......@@ -2719,10 +2719,10 @@ namespace Conformance {
}
OptionalForeignMessage.MergeFrom(other.OptionalForeignMessage);
}
if (other.OptionalNestedEnum != global::Conformance.TestAllTypes.Types.NestedEnum.FOO) {
if (other.OptionalNestedEnum != 0) {
OptionalNestedEnum = other.OptionalNestedEnum;
}
if (other.OptionalForeignEnum != global::Conformance.ForeignEnum.FOREIGN_FOO) {
if (other.OptionalForeignEnum != 0) {
OptionalForeignEnum = other.OptionalForeignEnum;
}
if (other.OptionalStringPiece.Length != 0) {
......@@ -3448,13 +3448,13 @@ namespace Conformance {
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial class Types {
public enum NestedEnum {
FOO = 0,
BAR = 1,
BAZ = 2,
[pbr::OriginalName("FOO")] Foo = 0,
[pbr::OriginalName("BAR")] Bar = 1,
[pbr::OriginalName("BAZ")] Baz = 2,
/// <summary>
/// Intentionally negative.
/// </summary>
NEG = -1,
[pbr::OriginalName("NEG")] Neg = -1,
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
......
......@@ -109,10 +109,10 @@ namespace Google.Protobuf.Conformance
{
switch (request.RequestedOutputFormat)
{
case global::Conformance.WireFormat.JSON:
case global::Conformance.WireFormat.Json:
var formatter = new JsonFormatter(new JsonFormatter.Settings(false, typeRegistry));
return new ConformanceResponse { JsonPayload = formatter.Format(message) };
case global::Conformance.WireFormat.PROTOBUF:
case global::Conformance.WireFormat.Protobuf:
return new ConformanceResponse { ProtobufPayload = message.ToByteString() };
default:
throw new Exception("Unsupported request output format: " + request.PayloadCase);
......
......@@ -58,7 +58,7 @@ namespace Google.Protobuf
new FieldCodecTestData<float>(FieldCodec.ForFloat(100), 1234.5f, "Float"),
new FieldCodecTestData<double>(FieldCodec.ForDouble(100), 1234567890.5d, "Double"),
new FieldCodecTestData<ForeignEnum>(
FieldCodec.ForEnum(100, t => (int) t, t => (ForeignEnum) t), ForeignEnum.FOREIGN_BAZ, "Enum"),
FieldCodec.ForEnum(100, t => (int) t, t => (ForeignEnum) t), ForeignEnum.ForeignBaz, "Enum"),
new FieldCodecTestData<ForeignMessage>(
FieldCodec.ForMessage(100, ForeignMessage.Parser), new ForeignMessage { C = 10 }, "Message"),
};
......
......@@ -66,13 +66,13 @@ namespace Google.Protobuf
Assert.AreEqual(0, message.SingleFixed32);
Assert.AreEqual(0L, message.SingleFixed64);
Assert.AreEqual(0.0f, message.SingleFloat);
Assert.AreEqual(ForeignEnum.FOREIGN_UNSPECIFIED, message.SingleForeignEnum);
Assert.AreEqual(ForeignEnum.ForeignUnspecified, message.SingleForeignEnum);
Assert.IsNull(message.SingleForeignMessage);
Assert.AreEqual(ImportEnum.IMPORT_ENUM_UNSPECIFIED, message.SingleImportEnum);
Assert.AreEqual(ImportEnum.Unspecified, message.SingleImportEnum);
Assert.IsNull(message.SingleImportMessage);
Assert.AreEqual(0, message.SingleInt32);
Assert.AreEqual(0L, message.SingleInt64);
Assert.AreEqual(TestAllTypes.Types.NestedEnum.NESTED_ENUM_UNSPECIFIED, message.SingleNestedEnum);
Assert.AreEqual(TestAllTypes.Types.NestedEnum.Unspecified, message.SingleNestedEnum);
Assert.IsNull(message.SingleNestedMessage);
Assert.IsNull(message.SinglePublicImportMessage);
Assert.AreEqual(0, message.SingleSfixed32);
......@@ -145,13 +145,13 @@ namespace Google.Protobuf
SingleFixed32 = 23,
SingleFixed64 = 1234567890123,
SingleFloat = 12.25f,
SingleForeignEnum = ForeignEnum.FOREIGN_BAR,
SingleForeignEnum = ForeignEnum.ForeignBar,
SingleForeignMessage = new ForeignMessage { C = 10 },
SingleImportEnum = ImportEnum.IMPORT_BAZ,
SingleImportEnum = ImportEnum.ImportBaz,
SingleImportMessage = new ImportMessage { D = 20 },
SingleInt32 = 100,
SingleInt64 = 3210987654321,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.FOO,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 },
SinglePublicImportMessage = new PublicImportMessage { E = 54 },
SingleSfixed32 = -123,
......@@ -179,13 +179,13 @@ namespace Google.Protobuf
RepeatedFixed32 = { uint.MaxValue, 23 },
RepeatedFixed64 = { ulong.MaxValue, 1234567890123 },
RepeatedFloat = { 100f, 12.25f },
RepeatedForeignEnum = { ForeignEnum.FOREIGN_FOO, ForeignEnum.FOREIGN_BAR },
RepeatedForeignEnum = { ForeignEnum.ForeignFoo, ForeignEnum.ForeignBar },
RepeatedForeignMessage = { new ForeignMessage(), new ForeignMessage { C = 10 } },
RepeatedImportEnum = { ImportEnum.IMPORT_BAZ, ImportEnum.IMPORT_ENUM_UNSPECIFIED },
RepeatedImportEnum = { ImportEnum.ImportBaz, ImportEnum.Unspecified },
RepeatedImportMessage = { new ImportMessage { D = 20 }, new ImportMessage { D = 25 } },
RepeatedInt32 = { 100, 200 },
RepeatedInt64 = { 3210987654321, long.MaxValue },
RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.FOO, TestAllTypes.Types.NestedEnum.NEG },
RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.Foo, TestAllTypes.Types.NestedEnum.Neg },
RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 35 }, new TestAllTypes.Types.NestedMessage { Bb = 10 } },
RepeatedPublicImportMessage = { new PublicImportMessage { E = 54 }, new PublicImportMessage { E = -1 } },
RepeatedSfixed32 = { -123, 123 },
......@@ -224,8 +224,8 @@ namespace Google.Protobuf
{ 5, new ForeignMessage() },
},
MapInt32Enum = {
{ 1, MapEnum.MAP_ENUM_BAR },
{ 2000, MapEnum.MAP_ENUM_FOO }
{ 1, MapEnum.Bar },
{ 2000, MapEnum.Foo }
}
};
......@@ -449,7 +449,7 @@ namespace Google.Protobuf
SingleFloat = 12.25f,
SingleInt32 = 100,
SingleInt64 = 3210987654321,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.FOO,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
SingleSfixed32 = -123,
SingleSfixed64 = -12345678901234,
SingleSint32 = -456,
......@@ -479,7 +479,7 @@ namespace Google.Protobuf
RepeatedFloat = { 100f, 12.25f },
RepeatedInt32 = { 100, 200 },
RepeatedInt64 = { 3210987654321, long.MaxValue },
RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.FOO, TestAllTypes.Types.NestedEnum.NEG },
RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.Foo, TestAllTypes.Types.NestedEnum.Neg },
RepeatedSfixed32 = { -123, 123 },
RepeatedSfixed64 = { -12345678901234, 12345678901234 },
RepeatedSint32 = { -456, 100 },
......
......@@ -75,13 +75,13 @@ namespace Google.Protobuf
SingleFixed32 = 23,
SingleFixed64 = 1234567890123,
SingleFloat = 12.25f,
SingleForeignEnum = ForeignEnum.FOREIGN_BAR,
SingleForeignEnum = ForeignEnum.ForeignBar,
SingleForeignMessage = new ForeignMessage { C = 10 },
SingleImportEnum = ImportEnum.IMPORT_BAZ,
SingleImportEnum = ImportEnum.ImportBaz,
SingleImportMessage = new ImportMessage { D = 20 },
SingleInt32 = 100,
SingleInt64 = 3210987654321,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.FOO,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 },
SinglePublicImportMessage = new PublicImportMessage { E = 54 },
SingleSfixed32 = -123,
......@@ -174,14 +174,14 @@ namespace Google.Protobuf
[Test]
public void UnknownEnumValueNumeric_RepeatedField()
{
var message = new TestAllTypes { RepeatedForeignEnum = { ForeignEnum.FOREIGN_BAZ, (ForeignEnum) 100, ForeignEnum.FOREIGN_FOO } };
var message = new TestAllTypes { RepeatedForeignEnum = { ForeignEnum.ForeignBaz, (ForeignEnum) 100, ForeignEnum.ForeignFoo } };
AssertJson("{ 'repeatedForeignEnum': [ 'FOREIGN_BAZ', 100, 'FOREIGN_FOO' ] }", JsonFormatter.Default.Format(message));
}
[Test]
public void UnknownEnumValueNumeric_MapField()
{
var message = new TestMap { MapInt32Enum = { { 1, MapEnum.MAP_ENUM_FOO }, { 2, (MapEnum) 100 }, { 3, MapEnum.MAP_ENUM_BAR } } };
var message = new TestMap { MapInt32Enum = { { 1, MapEnum.Foo }, { 2, (MapEnum) 100 }, { 3, MapEnum.Bar } } };
AssertJson("{ 'mapInt32Enum': { '1': 'MAP_ENUM_FOO', '2': 100, '3': 'MAP_ENUM_BAR' } }", JsonFormatter.Default.Format(message));
}
......
......@@ -142,8 +142,8 @@ namespace Google.Protobuf
[TestCase(typeof(DoubleValue), "1.5", 1.5d)]
public void Wrappers_Standalone(System.Type wrapperType, string json, object expectedValue)
{
IMessage parsed = (IMessage) Activator.CreateInstance(wrapperType);
IMessage expected = (IMessage) Activator.CreateInstance(wrapperType);
IMessage parsed = (IMessage)Activator.CreateInstance(wrapperType);
IMessage expected = (IMessage)Activator.CreateInstance(wrapperType);
JsonParser.Default.Merge(parsed, "null");
Assert.AreEqual(expected, parsed);
......@@ -886,9 +886,9 @@ namespace Google.Protobuf
}
[Test]
[TestCase("\"FOREIGN_BAR\"", ForeignEnum.FOREIGN_BAR)]
[TestCase("5", ForeignEnum.FOREIGN_BAR)]
[TestCase("100", (ForeignEnum) 100)]
[TestCase("\"FOREIGN_BAR\"", ForeignEnum.ForeignBar)]
[TestCase("5", ForeignEnum.ForeignBar)]
[TestCase("100", (ForeignEnum)100)]
public void EnumValid(string value, ForeignEnum expectedValue)
{
string json = "{ \"singleForeignEnum\": " + value + " }";
......
......@@ -195,7 +195,7 @@ namespace Google.Protobuf.Reflection
Assert.AreEqual(value, enumType.Values[1]);
Assert.AreEqual("FOREIGN_FOO", value.Name);
Assert.AreEqual(4, value.Number);
Assert.AreEqual((int) ForeignEnum.FOREIGN_FOO, value.Number);
Assert.AreEqual((int) ForeignEnum.ForeignFoo, value.Number);
Assert.AreEqual(value, enumType.FindValueByNumber(4));
Assert.Null(enumType.FindValueByName("NO_SUCH_VALUE"));
for (int i = 0; i < enumType.Values.Count; i++)
......
......@@ -128,7 +128,7 @@ namespace Google.Protobuf.Reflection
fields[TestAllTypes.SingleInt32FieldNumber].Accessor.SetValue(message, 500);
fields[TestAllTypes.SingleStringFieldNumber].Accessor.SetValue(message, "It's a string");
fields[TestAllTypes.SingleBytesFieldNumber].Accessor.SetValue(message, ByteString.CopyFrom(99, 98, 97));
fields[TestAllTypes.SingleForeignEnumFieldNumber].Accessor.SetValue(message, ForeignEnum.FOREIGN_FOO);
fields[TestAllTypes.SingleForeignEnumFieldNumber].Accessor.SetValue(message, ForeignEnum.ForeignFoo);
fields[TestAllTypes.SingleForeignMessageFieldNumber].Accessor.SetValue(message, new ForeignMessage { C = 12345 });
fields[TestAllTypes.SingleDoubleFieldNumber].Accessor.SetValue(message, 20150701.5);
......@@ -138,7 +138,7 @@ namespace Google.Protobuf.Reflection
SingleInt32 = 500,
SingleString = "It's a string",
SingleBytes = ByteString.CopyFrom(99, 98, 97),
SingleForeignEnum = ForeignEnum.FOREIGN_FOO,
SingleForeignEnum = ForeignEnum.ForeignFoo,
SingleForeignMessage = new ForeignMessage { C = 12345 },
SingleDouble = 20150701.5
};
......
......@@ -54,13 +54,13 @@ namespace Google.Protobuf
SingleFixed32 = 23,
SingleFixed64 = 1234567890123,
SingleFloat = 12.25f,
SingleForeignEnum = ForeignEnum.FOREIGN_BAR,
SingleForeignEnum = ForeignEnum.ForeignBar,
SingleForeignMessage = new ForeignMessage { C = 10 },
SingleImportEnum = ImportEnum.IMPORT_BAZ,
SingleImportEnum = ImportEnum.ImportBaz,
SingleImportMessage = new ImportMessage { D = 20 },
SingleInt32 = 100,
SingleInt64 = 3210987654321,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.FOO,
SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 },
SinglePublicImportMessage = new PublicImportMessage { E = 54 },
SingleSfixed32 = -123,
......@@ -76,13 +76,13 @@ namespace Google.Protobuf
RepeatedFixed32 = { UInt32.MaxValue, 23 },
RepeatedFixed64 = { UInt64.MaxValue, 1234567890123 },
RepeatedFloat = { 100f, 12.25f },
RepeatedForeignEnum = { ForeignEnum.FOREIGN_FOO, ForeignEnum.FOREIGN_BAR },
RepeatedForeignEnum = { ForeignEnum.ForeignFoo, ForeignEnum.ForeignBar },
RepeatedForeignMessage = { new ForeignMessage(), new ForeignMessage { C = 10 } },
RepeatedImportEnum = { ImportEnum.IMPORT_BAZ, ImportEnum.IMPORT_ENUM_UNSPECIFIED },
RepeatedImportEnum = { ImportEnum.ImportBaz, ImportEnum.Unspecified },
RepeatedImportMessage = { new ImportMessage { D = 20 }, new ImportMessage { D = 25 } },
RepeatedInt32 = { 100, 200 },
RepeatedInt64 = { 3210987654321, Int64.MaxValue },
RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.FOO, TestAllTypes.Types.NestedEnum.NEG },
RepeatedNestedEnum = { TestAllTypes.Types.NestedEnum.Foo, TestAllTypes.Types.NestedEnum.Neg },
RepeatedNestedMessage = { new TestAllTypes.Types.NestedMessage { Bb = 35 }, new TestAllTypes.Types.NestedMessage { Bb = 10 } },
RepeatedPublicImportMessage = { new PublicImportMessage { E = 54 }, new PublicImportMessage { E = -1 } },
RepeatedSfixed32 = { -123, 123 },
......
......@@ -43,8 +43,8 @@ namespace Google.Protobuf
NegativeEnumMessage msg = new NegativeEnumMessage
{
Value = NegativeEnum.MinusOne,
Values = { NegativeEnum.NEGATIVE_ENUM_ZERO, NegativeEnum.MinusOne, NegativeEnum.FiveBelow },
PackedValues = { NegativeEnum.NEGATIVE_ENUM_ZERO, NegativeEnum.MinusOne, NegativeEnum.FiveBelow }
Values = { NegativeEnum.Zero, NegativeEnum.MinusOne, NegativeEnum.FiveBelow },
PackedValues = { NegativeEnum.Zero, NegativeEnum.MinusOne, NegativeEnum.FiveBelow }
};
Assert.AreEqual(58, msg.CalculateSize());
......
......@@ -164,9 +164,9 @@ namespace Google.Protobuf.TestProtos {
}
#region Enums
public enum MapEnum {
MAP_ENUM_FOO = 0,
MAP_ENUM_BAR = 1,
MAP_ENUM_BAZ = 2,
[pbr::OriginalName("MAP_ENUM_FOO")] Foo = 0,
[pbr::OriginalName("MAP_ENUM_BAR")] Bar = 1,
[pbr::OriginalName("MAP_ENUM_BAZ")] Baz = 2,
}
#endregion
......@@ -1358,7 +1358,7 @@ namespace Google.Protobuf.TestProtos {
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial class Types {
public enum Type {
TYPE_FOO = 0,
[pbr::OriginalName("TYPE_FOO")] Foo = 0,
}
}
......
......@@ -42,10 +42,10 @@ namespace Google.Protobuf.TestProtos {
}
#region Enums
public enum ImportEnum {
IMPORT_ENUM_UNSPECIFIED = 0,
IMPORT_FOO = 7,
IMPORT_BAR = 8,
IMPORT_BAZ = 9,
[pbr::OriginalName("IMPORT_ENUM_UNSPECIFIED")] Unspecified = 0,
[pbr::OriginalName("IMPORT_FOO")] ImportFoo = 7,
[pbr::OriginalName("IMPORT_BAR")] ImportBar = 8,
[pbr::OriginalName("IMPORT_BAZ")] ImportBaz = 9,
}
#endregion
......
......@@ -66,14 +66,14 @@ namespace UnitTest.Issues.TestProtos {
}
#region Enums
public enum NegativeEnum {
NEGATIVE_ENUM_ZERO = 0,
FiveBelow = -5,
MinusOne = -1,
[pbr::OriginalName("NEGATIVE_ENUM_ZERO")] Zero = 0,
[pbr::OriginalName("FiveBelow")] FiveBelow = -5,
[pbr::OriginalName("MinusOne")] MinusOne = -1,
}
public enum DeprecatedEnum {
DEPRECATED_ZERO = 0,
one = 1,
[pbr::OriginalName("DEPRECATED_ZERO")] DeprecatedZero = 0,
[pbr::OriginalName("one")] One = 1,
}
#endregion
......@@ -356,7 +356,7 @@ namespace UnitTest.Issues.TestProtos {
/// <summary>Field number for the "value" field.</summary>
public const int ValueFieldNumber = 1;
private global::UnitTest.Issues.TestProtos.NegativeEnum value_ = global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO;
private global::UnitTest.Issues.TestProtos.NegativeEnum value_ = 0;
public global::UnitTest.Issues.TestProtos.NegativeEnum Value {
get { return value_; }
set {
......@@ -401,7 +401,7 @@ namespace UnitTest.Issues.TestProtos {
public override int GetHashCode() {
int hash = 1;
if (Value != global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO) hash ^= Value.GetHashCode();
if (Value != 0) hash ^= Value.GetHashCode();
hash ^= values_.GetHashCode();
hash ^= packedValues_.GetHashCode();
return hash;
......@@ -412,7 +412,7 @@ namespace UnitTest.Issues.TestProtos {
}
public void WriteTo(pb::CodedOutputStream output) {
if (Value != global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO) {
if (Value != 0) {
output.WriteRawTag(8);
output.WriteEnum((int) Value);
}
......@@ -422,7 +422,7 @@ namespace UnitTest.Issues.TestProtos {
public int CalculateSize() {
int size = 0;
if (Value != global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO) {
if (Value != 0) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Value);
}
size += values_.CalculateSize(_repeated_values_codec);
......@@ -434,7 +434,7 @@ namespace UnitTest.Issues.TestProtos {
if (other == null) {
return;
}
if (other.Value != global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO) {
if (other.Value != 0) {
Value = other.Value;
}
values_.Add(other.values_);
......@@ -620,7 +620,7 @@ namespace UnitTest.Issues.TestProtos {
/// <summary>Field number for the "EnumValue" field.</summary>
public const int EnumValueFieldNumber = 5;
private global::UnitTest.Issues.TestProtos.DeprecatedEnum enumValue_ = global::UnitTest.Issues.TestProtos.DeprecatedEnum.DEPRECATED_ZERO;
private global::UnitTest.Issues.TestProtos.DeprecatedEnum enumValue_ = 0;
[global::System.ObsoleteAttribute()]
public global::UnitTest.Issues.TestProtos.DeprecatedEnum EnumValue {
get { return enumValue_; }
......@@ -665,7 +665,7 @@ namespace UnitTest.Issues.TestProtos {
hash ^= primitiveArray_.GetHashCode();
if (messageValue_ != null) hash ^= MessageValue.GetHashCode();
hash ^= messageArray_.GetHashCode();
if (EnumValue != global::UnitTest.Issues.TestProtos.DeprecatedEnum.DEPRECATED_ZERO) hash ^= EnumValue.GetHashCode();
if (EnumValue != 0) hash ^= EnumValue.GetHashCode();
hash ^= enumArray_.GetHashCode();
return hash;
}
......@@ -685,7 +685,7 @@ namespace UnitTest.Issues.TestProtos {
output.WriteMessage(MessageValue);
}
messageArray_.WriteTo(output, _repeated_messageArray_codec);
if (EnumValue != global::UnitTest.Issues.TestProtos.DeprecatedEnum.DEPRECATED_ZERO) {
if (EnumValue != 0) {
output.WriteRawTag(40);
output.WriteEnum((int) EnumValue);
}
......@@ -702,7 +702,7 @@ namespace UnitTest.Issues.TestProtos {
size += 1 + pb::CodedOutputStream.ComputeMessageSize(MessageValue);
}
size += messageArray_.CalculateSize(_repeated_messageArray_codec);
if (EnumValue != global::UnitTest.Issues.TestProtos.DeprecatedEnum.DEPRECATED_ZERO) {
if (EnumValue != 0) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) EnumValue);
}
size += enumArray_.CalculateSize(_repeated_enumArray_codec);
......@@ -724,7 +724,7 @@ namespace UnitTest.Issues.TestProtos {
MessageValue.MergeFrom(other.MessageValue);
}
messageArray_.Add(other.messageArray_);
if (other.EnumValue != global::UnitTest.Issues.TestProtos.DeprecatedEnum.DEPRECATED_ZERO) {
if (other.EnumValue != 0) {
EnumValue = other.EnumValue;
}
enumArray_.Add(other.enumArray_);
......@@ -1435,7 +1435,7 @@ namespace UnitTest.Issues.TestProtos {
public const int NameFieldNumber = 1;
private string name_ = "";
/// <summary>
/// json_name field options are not properly handled during deserialization
/// Message for testing the effects for of the json_name option
/// </summary>
public string Name {
get { return name_; }
......
......@@ -117,6 +117,7 @@
<Compile Include="Reflection\MethodDescriptor.cs" />
<Compile Include="Reflection\OneofAccessor.cs" />
<Compile Include="Reflection\OneofDescriptor.cs" />
<Compile Include="Reflection\OriginalNameAttribute.cs" />
<Compile Include="Reflection\PackageDescriptor.cs" />
<Compile Include="Reflection\PartialClasses.cs" />
<Compile Include="Reflection\ReflectionUtil.cs" />
......
......@@ -33,10 +33,12 @@
<dependency id="System.Linq.Expressions" version="4.0.0" />
<dependency id="System.ObjectModel" version="4.0.0" />
<dependency id="System.Reflection" version="4.0.0" />
<dependency id="System.Reflection.Extensions" version="4.0.0" />
<dependency id="System.Runtime" version="4.0.0" />
<dependency id="System.Runtime.Extensions" version="4.0.0" />
<dependency id="System.Text.Encoding" version="4.0.0" />
<dependency id="System.Text.RegularExpressions" version="4.0.0" />
<dependency id="System.Threading" version="4.0.0" />
</group>
</dependencies>
</metadata>
......
......@@ -39,6 +39,7 @@ using Google.Protobuf.WellKnownTypes;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
namespace Google.Protobuf
{
......@@ -420,9 +421,10 @@ namespace Google.Protobuf
}
else if (value is System.Enum)
{
if (System.Enum.IsDefined(value.GetType(), value))
string name = OriginalEnumValueHelper.GetOriginalName(value);
if (name != null)
{
WriteString(writer, value.ToString());
WriteString(writer, name);
}
else
{
......@@ -877,5 +879,44 @@ namespace Google.Protobuf
TypeRegistry = ProtoPreconditions.CheckNotNull(typeRegistry, nameof(typeRegistry));
}
}
// Effectively a cache of mapping from enum values to the original name as specified in the proto file,
// fetched by reflection.
// The need for this is unfortunate, as is its unbounded size, but realistically it shouldn't cause issues.
private static class OriginalEnumValueHelper
{
// TODO: In the future we might want to use ConcurrentDictionary, at the point where all
// the platforms we target have it.
private static readonly Dictionary<System.Type, Dictionary<object, string>> dictionaries
= new Dictionary<System.Type, Dictionary<object, string>>();
internal static string GetOriginalName(object value)
{
var enumType = value.GetType();
Dictionary<object, string> nameMapping;
lock (dictionaries)
{
if (!dictionaries.TryGetValue(enumType, out nameMapping))
{
nameMapping = GetNameMapping(enumType);
dictionaries[enumType] = nameMapping;
}
}
string originalName;
// If this returns false, originalName will be null, which is what we want.
nameMapping.TryGetValue(value, out originalName);
return originalName;
}
private static Dictionary<object, string> GetNameMapping(System.Type enumType) =>
enumType.GetTypeInfo().DeclaredFields
.Where(f => f.IsStatic)
.ToDictionary(f => f.GetValue(null),
f => f.GetCustomAttributes<OriginalNameAttribute>()
.FirstOrDefault()
// If the attribute hasn't been applied, fall back to the name of the field.
?.Name ?? f.Name);
}
}
}
......@@ -133,41 +133,41 @@ namespace Google.Protobuf.Reflection
{
switch (type)
{
case FieldDescriptorProto.Types.Type.TYPE_DOUBLE:
case FieldDescriptorProto.Types.Type.Double:
return FieldType.Double;
case FieldDescriptorProto.Types.Type.TYPE_FLOAT:
case FieldDescriptorProto.Types.Type.Float:
return FieldType.Float;
case FieldDescriptorProto.Types.Type.TYPE_INT64:
case FieldDescriptorProto.Types.Type.Int64:
return FieldType.Int64;
case FieldDescriptorProto.Types.Type.TYPE_UINT64:
case FieldDescriptorProto.Types.Type.Uint64:
return FieldType.UInt64;
case FieldDescriptorProto.Types.Type.TYPE_INT32:
case FieldDescriptorProto.Types.Type.Int32:
return FieldType.Int32;
case FieldDescriptorProto.Types.Type.TYPE_FIXED64:
case FieldDescriptorProto.Types.Type.Fixed64:
return FieldType.Fixed64;
case FieldDescriptorProto.Types.Type.TYPE_FIXED32:
case FieldDescriptorProto.Types.Type.Fixed32:
return FieldType.Fixed32;
case FieldDescriptorProto.Types.Type.TYPE_BOOL:
case FieldDescriptorProto.Types.Type.Bool:
return FieldType.Bool;
case FieldDescriptorProto.Types.Type.TYPE_STRING:
case FieldDescriptorProto.Types.Type.String:
return FieldType.String;
case FieldDescriptorProto.Types.Type.TYPE_GROUP:
case FieldDescriptorProto.Types.Type.Group:
return FieldType.Group;
case FieldDescriptorProto.Types.Type.TYPE_MESSAGE:
case FieldDescriptorProto.Types.Type.Message:
return FieldType.Message;
case FieldDescriptorProto.Types.Type.TYPE_BYTES:
case FieldDescriptorProto.Types.Type.Bytes:
return FieldType.Bytes;
case FieldDescriptorProto.Types.Type.TYPE_UINT32:
case FieldDescriptorProto.Types.Type.Uint32:
return FieldType.UInt32;
case FieldDescriptorProto.Types.Type.TYPE_ENUM:
case FieldDescriptorProto.Types.Type.Enum:
return FieldType.Enum;
case FieldDescriptorProto.Types.Type.TYPE_SFIXED32:
case FieldDescriptorProto.Types.Type.Sfixed32:
return FieldType.SFixed32;
case FieldDescriptorProto.Types.Type.TYPE_SFIXED64:
case FieldDescriptorProto.Types.Type.Sfixed64:
return FieldType.SFixed64;
case FieldDescriptorProto.Types.Type.TYPE_SINT32:
case FieldDescriptorProto.Types.Type.Sint32:
return FieldType.SInt32;
case FieldDescriptorProto.Types.Type.TYPE_SINT64:
case FieldDescriptorProto.Types.Type.Sint64:
return FieldType.SInt64;
default:
throw new ArgumentException("Invalid type specified");
......@@ -177,7 +177,7 @@ namespace Google.Protobuf.Reflection
/// <summary>
/// Returns <c>true</c> if this field is a repeated field; <c>false</c> otherwise.
/// </summary>
public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.LABEL_REPEATED;
public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated;
/// <summary>
/// Returns <c>true</c> if this field is a map field; <c>false</c> otherwise.
......
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
namespace Google.Protobuf.Reflection
{
/// <summary>
/// Specifies the original name (in the .proto file) of a named element,
/// such as an enum value.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class OriginalNameAttribute : Attribute
{
/// <summary>
/// The name of the element in the .proto file.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Constructs a new attribute instance for the given name.
/// </summary>
/// <param name="name">The name of the element in the .proto file.</param>
public OriginalNameAttribute(string name)
{
Name = ProtoPreconditions.CheckNotNull(name, nameof(name));
}
}
}
\ No newline at end of file
......@@ -185,7 +185,7 @@ namespace Google.Protobuf.WellKnownTypes {
/// <summary>Field number for the "syntax" field.</summary>
public const int SyntaxFieldNumber = 7;
private global::Google.Protobuf.WellKnownTypes.Syntax syntax_ = global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2;
private global::Google.Protobuf.WellKnownTypes.Syntax syntax_ = 0;
/// <summary>
/// The source syntax of the service.
/// </summary>
......@@ -225,7 +225,7 @@ namespace Google.Protobuf.WellKnownTypes {
if (Version.Length != 0) hash ^= Version.GetHashCode();
if (sourceContext_ != null) hash ^= SourceContext.GetHashCode();
hash ^= mixins_.GetHashCode();
if (Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) hash ^= Syntax.GetHashCode();
if (Syntax != 0) hash ^= Syntax.GetHashCode();
return hash;
}
......@@ -249,7 +249,7 @@ namespace Google.Protobuf.WellKnownTypes {
output.WriteMessage(SourceContext);
}
mixins_.WriteTo(output, _repeated_mixins_codec);
if (Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) {
if (Syntax != 0) {
output.WriteRawTag(56);
output.WriteEnum((int) Syntax);
}
......@@ -269,7 +269,7 @@ namespace Google.Protobuf.WellKnownTypes {
size += 1 + pb::CodedOutputStream.ComputeMessageSize(SourceContext);
}
size += mixins_.CalculateSize(_repeated_mixins_codec);
if (Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) {
if (Syntax != 0) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Syntax);
}
return size;
......@@ -294,7 +294,7 @@ namespace Google.Protobuf.WellKnownTypes {
SourceContext.MergeFrom(other.SourceContext);
}
mixins_.Add(other.mixins_);
if (other.Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) {
if (other.Syntax != 0) {
Syntax = other.Syntax;
}
}
......@@ -458,7 +458,7 @@ namespace Google.Protobuf.WellKnownTypes {
/// <summary>Field number for the "syntax" field.</summary>
public const int SyntaxFieldNumber = 7;
private global::Google.Protobuf.WellKnownTypes.Syntax syntax_ = global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2;
private global::Google.Protobuf.WellKnownTypes.Syntax syntax_ = 0;
/// <summary>
/// The source syntax of this method.
/// </summary>
......@@ -498,7 +498,7 @@ namespace Google.Protobuf.WellKnownTypes {
if (ResponseTypeUrl.Length != 0) hash ^= ResponseTypeUrl.GetHashCode();
if (ResponseStreaming != false) hash ^= ResponseStreaming.GetHashCode();
hash ^= options_.GetHashCode();
if (Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) hash ^= Syntax.GetHashCode();
if (Syntax != 0) hash ^= Syntax.GetHashCode();
return hash;
}
......@@ -528,7 +528,7 @@ namespace Google.Protobuf.WellKnownTypes {
output.WriteBool(ResponseStreaming);
}
options_.WriteTo(output, _repeated_options_codec);
if (Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) {
if (Syntax != 0) {
output.WriteRawTag(56);
output.WriteEnum((int) Syntax);
}
......@@ -552,7 +552,7 @@ namespace Google.Protobuf.WellKnownTypes {
size += 1 + 1;
}
size += options_.CalculateSize(_repeated_options_codec);
if (Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) {
if (Syntax != 0) {
size += 1 + pb::CodedOutputStream.ComputeEnumSize((int) Syntax);
}
return size;
......@@ -578,7 +578,7 @@ namespace Google.Protobuf.WellKnownTypes {
ResponseStreaming = other.ResponseStreaming;
}
options_.Add(other.options_);
if (other.Syntax != global::Google.Protobuf.WellKnownTypes.Syntax.SYNTAX_PROTO2) {
if (other.Syntax != 0) {
Syntax = other.Syntax;
}
}
......
......@@ -59,7 +59,7 @@ namespace Google.Protobuf.WellKnownTypes {
/// <summary>
/// Null value.
/// </summary>
NULL_VALUE = 0,
[pbr::OriginalName("NULL_VALUE")] NullValue = 0,
}
#endregion
......@@ -234,7 +234,7 @@ namespace Google.Protobuf.WellKnownTypes {
/// Represents a null value.
/// </summary>
public global::Google.Protobuf.WellKnownTypes.NullValue NullValue {
get { return kindCase_ == KindOneofCase.NullValue ? (global::Google.Protobuf.WellKnownTypes.NullValue) kind_ : global::Google.Protobuf.WellKnownTypes.NullValue.NULL_VALUE; }
get { return kindCase_ == KindOneofCase.NullValue ? (global::Google.Protobuf.WellKnownTypes.NullValue) kind_ : 0; }
set {
kind_ = value;
kindCase_ = KindOneofCase.NullValue;
......
......@@ -41,6 +41,7 @@
#include <google/protobuf/compiler/csharp/csharp_doc_comment.h>
#include <google/protobuf/compiler/csharp/csharp_enum.h>
#include <google/protobuf/compiler/csharp/csharp_helpers.h>
#include <google/protobuf/compiler/csharp/csharp_options.h>
using google::protobuf::internal::scoped_ptr;
......@@ -64,10 +65,23 @@ void EnumGenerator::Generate(io::Printer* printer) {
"access_level", class_access_level(),
"name", descriptor_->name());
printer->Indent();
std::set<string> used_names;
for (int i = 0; i < descriptor_->value_count(); i++) {
WriteEnumValueDocComment(printer, descriptor_->value(i));
printer->Print("$name$ = $number$,\n",
"name", descriptor_->value(i)->name(),
string original_name = descriptor_->value(i)->name();
string name = options()->legacy_enum_values
? descriptor_->value(i)->name()
: GetEnumValueName(descriptor_->name(), descriptor_->value(i)->name());
// Make sure we don't get any duplicate names due to prefix removal.
while (!used_names.insert(name).second) {
// It's possible we'll end up giving this warning multiple times, but that's better than not at all.
GOOGLE_LOG(WARNING) << "Duplicate enum value " << name << " (originally " << original_name
<< ") in " << descriptor_->name() << "; adding underscore to distinguish";
name += "_";
}
printer->Print("[pbr::OriginalName(\"$original_name$\")] $name$ = $number$,\n",
"original_name", original_name,
"name", name,
"number", SimpleItoa(descriptor_->value(i)->number()));
}
printer->Outdent();
......
......@@ -306,7 +306,9 @@ std::string FieldGeneratorBase::default_value() {
std::string FieldGeneratorBase::default_value(const FieldDescriptor* descriptor) {
switch (descriptor->type()) {
case FieldDescriptor::TYPE_ENUM:
return type_name() + "." + descriptor->default_value_enum()->name();
// All proto3 enums have a default value of 0, and there's an implicit conversion from the constant 0 to
// any C# enum. This means we don't need to work out what we actually mapped the enum value name to.
return "0";
case FieldDescriptor::TYPE_MESSAGE:
case FieldDescriptor::TYPE_GROUP:
if (IsWrapperType(descriptor)) {
......
......@@ -83,6 +83,9 @@ bool Generator::Generate(
cli_options.base_namespace_specified = true;
} else if (options[i].first == "internal_access") {
cli_options.internal_access = true;
} else if (options[i].first == "legacy_enum_values") {
// TODO: Remove this before final release
cli_options.legacy_enum_values = true;
} else {
*error = "Unknown generator option: " + options[i].first;
return false;
......
......@@ -30,8 +30,8 @@
#include <memory>
#include <google/protobuf/compiler/ruby/ruby_generator.h>
#include <google/protobuf/compiler/command_line_interface.h>
#include <google/protobuf/compiler/csharp/csharp_helpers.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/io/printer.h>
......@@ -45,7 +45,23 @@ namespace compiler {
namespace csharp {
namespace {
// TODO(jtattermusch): add some tests.
TEST(CSharpEnumValue, PascalCasedPrefixStripping) {
EXPECT_EQ("Bar", GetEnumValueName("Foo", "BAR"));
EXPECT_EQ("BarBaz", GetEnumValueName("Foo", "BAR_BAZ"));
EXPECT_EQ("Bar", GetEnumValueName("Foo", "FOO_BAR"));
EXPECT_EQ("Bar", GetEnumValueName("Foo", "FOO__BAR"));
EXPECT_EQ("BarBaz", GetEnumValueName("Foo", "FOO_BAR_BAZ"));
EXPECT_EQ("BarBaz", GetEnumValueName("Foo", "Foo_BarBaz"));
EXPECT_EQ("Bar", GetEnumValueName("FO_O", "FOO_BAR"));
EXPECT_EQ("Bar", GetEnumValueName("FOO", "F_O_O_BAR"));
EXPECT_EQ("Bar", GetEnumValueName("Foo", "BAR"));
EXPECT_EQ("BarBaz", GetEnumValueName("Foo", "BAR_BAZ"));
EXPECT_EQ("Foo", GetEnumValueName("Foo", "FOO"));
EXPECT_EQ("Foo", GetEnumValueName("Foo", "FOO___"));
// Identifiers can't start with digits
EXPECT_EQ("_2Bar", GetEnumValueName("Foo", "FOO_2_BAR"));
EXPECT_EQ("_2", GetEnumValueName("Foo", "FOO___2"));
}
} // namespace
} // namespace csharp
......
......@@ -178,6 +178,104 @@ std::string UnderscoresToPascalCase(const std::string& input) {
return UnderscoresToCamelCase(input, true);
}
// Convert a string which is expected to be SHOUTY_CASE (but may not be *precisely* shouty)
// into a PascalCase string. Precise rules implemented:
// Previous input character Current character Case
// Any Non-alphanumeric Skipped
// None - first char of input Alphanumeric Upper
// Non-letter (e.g. _ or 1) Alphanumeric Upper
// Numeric Alphanumeric Upper
// Lower letter Alphanumeric Same as current
// Upper letter Alphanumeric Lower
std::string ShoutyToPascalCase(const std::string& input) {
string result;
// Simple way of implementing "always start with upper"
char previous = '_';
for (int i = 0; i < input.size(); i++) {
char current = input[i];
if (!ascii_isalnum(current)) {
previous = current;
continue;
}
if (!ascii_isalnum(previous)) {
result += ascii_toupper(current);
} else if (ascii_isdigit(previous)) {
result += ascii_toupper(current);
} else if (ascii_islower(previous)) {
result += current;
} else {
result += ascii_tolower(current);
}
previous = current;
}
return result;
}
// Attempt to remove a prefix from a value, ignoring casing and skipping underscores.
// (foo, foo_bar) => bar - underscore after prefix is skipped
// (FOO, foo_bar) => bar - casing is ignored
// (foo_bar, foobarbaz) => baz - underscore in prefix is ignored
// (foobar, foo_barbaz) => baz - underscore in value is ignored
// (foo, bar) => bar - prefix isn't matched; return original value
std::string TryRemovePrefix(const std::string& prefix, const std::string& value) {
// First normalize to a lower-case no-underscores prefix to match against
std::string prefix_to_match = "";
for (size_t i = 0; i < prefix.size(); i++) {
if (prefix[i] != '_') {
prefix_to_match += ascii_tolower(prefix[i]);
}
}
// This keeps track of how much of value we've consumed
size_t prefix_index, value_index;
for (prefix_index = 0, value_index = 0;
prefix_index < prefix_to_match.size() && value_index < value.size();
value_index++) {
// Skip over underscores in the value
if (value[value_index] == '_') {
continue;
}
if (ascii_tolower(value[value_index]) != prefix_to_match[prefix_index++]) {
// Failed to match the prefix - bail out early.
return value;
}
}
// If we didn't finish looking through the prefix, we can't strip it.
if (prefix_index < prefix_to_match.size()) {
return value;
}
// Step over any underscores after the prefix
while (value_index < value.size() && value[value_index] == '_') {
value_index++;
}
// If there's nothing left (e.g. it was a prefix with only underscores afterwards), don't strip.
if (value_index == value.size()) {
return value;
}
return value.substr(value_index);
}
// Format the enum value name in a pleasant way for C#:
// - Strip the enum name as a prefix if possible
// - Convert to PascalCase.
// For example, an enum called Color with a value of COLOR_BLUE should
// result in an enum value in C# called just Blue
std::string GetEnumValueName(const std::string& enum_name, const std::string& enum_value_name) {
std::string stripped = TryRemovePrefix(enum_name, enum_value_name);
std::string result = ShoutyToPascalCase(stripped);
// Just in case we have an enum name of FOO and a value of FOO_2... make sure the returned
// string is a valid identifier.
if (ascii_isdigit(result[0])) {
result = "_" + result;
}
return result;
}
std::string ToCSharpName(const std::string& name, const FileDescriptor* file) {
std::string result = GetFileNamespace(file);
if (result != "") {
......
......@@ -36,6 +36,7 @@
#define GOOGLE_PROTOBUF_COMPILER_CSHARP_HELPERS_H__
#include <string>
#include <google/protobuf/stubs/port.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/compiler/code_generator.h>
......@@ -93,6 +94,10 @@ inline std::string UnderscoresToCamelCase(const std::string& input, bool cap_nex
std::string UnderscoresToPascalCase(const std::string& input);
// Note that we wouldn't normally want to export this (we're not expecting
// it to be used outside libprotoc itself) but this exposes it for testing.
std::string LIBPROTOBUF_EXPORT GetEnumValueName(const std::string& enum_name, const std::string& enum_value_name);
// TODO(jtattermusch): perhaps we could move this to strutil
std::string StringToBase64(const std::string& input);
......
......@@ -45,7 +45,8 @@ struct Options {
file_extension(".cs"),
base_namespace(""),
base_namespace_specified(false),
internal_access(false) {
internal_access(false),
legacy_enum_values(false) {
}
// Extension of the generated file. Defaults to ".cs"
string file_extension;
......@@ -68,6 +69,12 @@ struct Options {
// Whether the generated classes should have accessibility level of "internal".
// Defaults to false that generates "public" classes.
bool internal_access;
// By default, C# codegen now uses PascalCased enum values names, after
// removing the enum type name as a prefix (if it *is* a prefix of the value).
// Setting this option reverts to the previous behavior of just copying the
// value name specified in the .proto file, allowing gradual migration.
// This option will be removed before final release.
bool legacy_enum_values;
};
} // namespace csharp
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment