Commit 6b01539d authored by Jon Skeet's avatar Jon Skeet

Merge pull request #543 from jskeet/proto3-map

Proto3 map support for C#
parents 68a4ee26 286edc0f
...@@ -19,6 +19,7 @@ set(libprotoc_files ...@@ -19,6 +19,7 @@ set(libprotoc_files
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_field_base.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_field_base.cc
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_generator.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_generator.cc
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_helpers.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_helpers.cc
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_map_field.cc
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message.cc
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message_field.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_message_field.cc
${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc ${protobuf_source_dir}/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc
......
...@@ -43,10 +43,12 @@ $PROTOC -Isrc --csharp_out=csharp/src/ProtocolBuffers/DescriptorProtos \ ...@@ -43,10 +43,12 @@ $PROTOC -Isrc --csharp_out=csharp/src/ProtocolBuffers/DescriptorProtos \
rm src/google/protobuf/descriptor_proto_file.proto rm src/google/protobuf/descriptor_proto_file.proto
$PROTOC -Isrc --csharp_out=csharp/src/ProtocolBuffers.Test/TestProtos \ $PROTOC -Isrc --csharp_out=csharp/src/ProtocolBuffers.Test/TestProtos \
src/google/protobuf/map_unittest_proto3.proto \
src/google/protobuf/unittest_proto3.proto \ src/google/protobuf/unittest_proto3.proto \
src/google/protobuf/unittest_import_proto3.proto \ src/google/protobuf/unittest_import_proto3.proto \
src/google/protobuf/unittest_import_public_proto3.proto src/google/protobuf/unittest_import_public_proto3.proto
$PROTOC -Icsharp/protos/extest --csharp_out=csharp/src/ProtocolBuffers.Test/TestProtos \ $PROTOC -Icsharp/protos/extest --csharp_out=csharp/src/ProtocolBuffers.Test/TestProtos \
csharp/protos/extest/unittest_issues.proto csharp/protos/extest/unittest_issues.proto
......
...@@ -155,7 +155,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -155,7 +155,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
if (Name.Length != 0) hash ^= Name.GetHashCode(); if (Name.Length != 0) hash ^= Name.GetHashCode();
if (Id != 0) hash ^= Id.GetHashCode(); if (Id != 0) hash ^= Id.GetHashCode();
if (Email.Length != 0) hash ^= Email.GetHashCode(); if (Email.Length != 0) hash ^= Email.GetHashCode();
...@@ -200,6 +200,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -200,6 +200,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
} }
return size; return size;
} }
public void MergeFrom(Person other) { public void MergeFrom(Person other) {
if (other == null) { if (other == null) {
return; return;
...@@ -329,7 +330,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -329,7 +330,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
if (Number.Length != 0) hash ^= Number.GetHashCode(); if (Number.Length != 0) hash ^= Number.GetHashCode();
if (Type != global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType.HOME) hash ^= Type.GetHashCode(); if (Type != global::Google.ProtocolBuffers.Examples.AddressBook.Person.Types.PhoneType.HOME) hash ^= Type.GetHashCode();
return hash; return hash;
...@@ -356,6 +357,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -356,6 +357,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
} }
return size; return size;
} }
public void MergeFrom(PhoneNumber other) { public void MergeFrom(PhoneNumber other) {
if (other == null) { if (other == null) {
return; return;
...@@ -456,7 +458,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -456,7 +458,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
hash ^= person_.GetHashCode(); hash ^= person_.GetHashCode();
return hash; return hash;
} }
...@@ -477,6 +479,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook { ...@@ -477,6 +479,7 @@ namespace Google.ProtocolBuffers.Examples.AddressBook {
} }
return size; return size;
} }
public void MergeFrom(AddressBook other) { public void MergeFrom(AddressBook other) {
if (other == null) { if (other == null) {
return; return;
......
This diff is collapsed.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Google.Protobuf.Collections;
using Google.Protobuf.TestProtos; using Google.Protobuf.TestProtos;
using NUnit.Framework; using NUnit.Framework;
namespace Google.Protobuf namespace Google.Protobuf.Collections
{ {
public class RepeatedFieldTest public class RepeatedFieldTest
{ {
......
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
namespace Google.Protobuf
{
/// <summary>
/// Helper methods when testing equality. NUnit's Assert.AreEqual and
/// Assert.AreNotEqual methods try to be clever with collections, which can
/// be annoying...
/// </summary>
internal static class EqualityTester
{
public static void AssertEquality<T>(T first, T second) where T : IEquatable<T>
{
Assert.IsTrue(first.Equals(second));
Assert.AreEqual(first.GetHashCode(), second.GetHashCode());
}
public static void AssertInequality<T>(T first, T second) where T : IEquatable<T>
{
Assert.IsFalse(first.Equals(second));
// While this isn't a requirement, the chances of this test failing due to
// coincidence rather than a bug are very small.
Assert.AreNotEqual(first.GetHashCode(), second.GetHashCode());
}
}
}
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2015 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.Collections.Generic;
using System.IO;
using Google.Protobuf.TestProtos;
using NUnit.Framework;
namespace Google.Protobuf
{
public class FieldCodecTest
{
private static readonly List<ICodecTestData> Codecs = new List<ICodecTestData>
{
new FieldCodecTestData<bool>(FieldCodec.ForBool(100), true, "Bool"),
new FieldCodecTestData<string>(FieldCodec.ForString(100), "sample", "String"),
new FieldCodecTestData<ByteString>(FieldCodec.ForBytes(100), ByteString.CopyFrom(1, 2, 3), "Bytes"),
new FieldCodecTestData<int>(FieldCodec.ForInt32(100), -1000, "Int32"),
new FieldCodecTestData<int>(FieldCodec.ForSInt32(100), -1000, "SInt32"),
new FieldCodecTestData<int>(FieldCodec.ForSFixed32(100), -1000, "SFixed32"),
new FieldCodecTestData<uint>(FieldCodec.ForUInt32(100), 1234, "UInt32"),
new FieldCodecTestData<uint>(FieldCodec.ForFixed32(100), 1234, "Fixed32"),
new FieldCodecTestData<long>(FieldCodec.ForInt64(100), -1000, "Int64"),
new FieldCodecTestData<long>(FieldCodec.ForSInt64(100), -1000, "SInt64"),
new FieldCodecTestData<long>(FieldCodec.ForSFixed64(100), -1000, "SFixed64"),
new FieldCodecTestData<ulong>(FieldCodec.ForUInt64(100), 1234, "UInt64"),
new FieldCodecTestData<ulong>(FieldCodec.ForFixed64(100), 1234, "Fixed64"),
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"),
new FieldCodecTestData<ForeignMessage>(
FieldCodec.ForMessage(100, ForeignMessage.Parser), new ForeignMessage { C = 10 }, "Message"),
};
[Test, TestCaseSource("Codecs")]
public void RoundTrip(ICodecTestData codec)
{
codec.TestRoundTrip();
}
[Test, TestCaseSource("Codecs")]
public void CalculateSize(ICodecTestData codec)
{
codec.TestCalculateSize();
}
[Test, TestCaseSource("Codecs")]
public void DefaultValue(ICodecTestData codec)
{
codec.TestDefaultValue();
}
public interface ICodecTestData
{
void TestRoundTrip();
void TestCalculateSize();
void TestDefaultValue();
}
public class FieldCodecTestData<T> : ICodecTestData
{
private readonly FieldCodec<T> codec;
private readonly T sampleValue;
private readonly string name;
public FieldCodecTestData(FieldCodec<T> codec, T sampleValue, string name)
{
this.codec = codec;
this.sampleValue = sampleValue;
this.name = name;
}
public void TestRoundTrip()
{
var stream = new MemoryStream();
var codedOutput = CodedOutputStream.CreateInstance(stream);
codec.Write(codedOutput, sampleValue);
codedOutput.Flush();
stream.Position = 0;
var codedInput = CodedInputStream.CreateInstance(stream);
uint tag;
Assert.IsTrue(codedInput.ReadTag(out tag));
Assert.AreEqual(codec.Tag, tag);
Assert.AreEqual(sampleValue, codec.Read(codedInput));
Assert.IsTrue(codedInput.IsAtEnd);
}
public void TestCalculateSize()
{
var stream = new MemoryStream();
var codedOutput = CodedOutputStream.CreateInstance(stream);
codec.Write(codedOutput, sampleValue);
codedOutput.Flush();
Assert.AreEqual(stream.Position, codec.CalculateSize(sampleValue));
}
public void TestDefaultValue()
{
var stream = new MemoryStream();
var codedOutput = CodedOutputStream.CreateInstance(stream);
codec.Write(codedOutput, codec.DefaultValue);
codedOutput.Flush();
Assert.AreEqual(0, stream.Position);
Assert.AreEqual(0, codec.CalculateSize(codec.DefaultValue));
if (typeof(T).IsValueType)
{
Assert.AreEqual(default(T), codec.DefaultValue);
}
}
public string Description { get { return name; } }
public override string ToString()
{
return name;
}
}
}
}
using System; using System;
using System.IO;
using Google.Protobuf.TestProtos; using Google.Protobuf.TestProtos;
using NUnit.Framework; using NUnit.Framework;
...@@ -9,6 +10,15 @@ namespace Google.Protobuf ...@@ -9,6 +10,15 @@ namespace Google.Protobuf
/// </summary> /// </summary>
public class GeneratedMessageTest public class GeneratedMessageTest
{ {
[Test]
public void EmptyMessageFieldDistinctFromMissingMessageField()
{
// This demonstrates what we're really interested in...
var message1 = new TestAllTypes { SingleForeignMessage = new ForeignMessage() };
var message2 = new TestAllTypes(); // SingleForeignMessage is null
EqualityTester.AssertInequality(message1, message2);
}
[Test] [Test]
public void DefaultValues() public void DefaultValues()
{ {
...@@ -146,6 +156,206 @@ namespace Google.Protobuf ...@@ -146,6 +156,206 @@ namespace Google.Protobuf
Assert.AreEqual(message, parsed); Assert.AreEqual(message, parsed);
} }
// Note that not every map within map_unittest_proto3 is used. They all go through very
// similar code paths. The fact that all maps are present is validation that we have codecs
// for every type.
[Test]
public void RoundTrip_Maps()
{
var message = new TestMap
{
MapBoolBool = {
{ false, true },
{ true, false }
},
MapInt32Bytes = {
{ 5, ByteString.CopyFrom(6, 7, 8) },
{ 25, ByteString.CopyFrom(1, 2, 3, 4, 5) },
{ 10, ByteString.Empty }
},
MapInt32ForeignMessage = {
{ 0, new ForeignMessage { C = 10 } },
{ 5, null },
},
MapInt32Enum = {
{ 1, MapEnum.MAP_ENUM_BAR },
{ 2000, MapEnum.MAP_ENUM_FOO }
}
};
byte[] bytes = message.ToByteArray();
TestMap parsed = TestMap.Parser.ParseFrom(bytes);
Assert.AreEqual(message, parsed);
}
[Test]
public void MapWithEmptyEntry()
{
var message = new TestMap
{
MapInt32Bytes = { { 0, ByteString.Empty } }
};
byte[] bytes = message.ToByteArray();
Assert.AreEqual(2, bytes.Length); // Tag for field entry (1 byte), length of entry (0; 1 byte)
var parsed = TestMap.Parser.ParseFrom(bytes);
Assert.AreEqual(1, parsed.MapInt32Bytes.Count);
Assert.AreEqual(ByteString.Empty, parsed.MapInt32Bytes[0]);
}
[Test]
public void MapWithOnlyValue()
{
// Hand-craft the stream to contain a single entry with just a value.
var memoryStream = new MemoryStream();
var output = CodedOutputStream.CreateInstance(memoryStream);
output.WriteTag(TestMap.MapInt32ForeignMessageFieldNumber, WireFormat.WireType.LengthDelimited);
var nestedMessage = new ForeignMessage { C = 20 };
// Size of the entry (tag, size written by WriteMessage, data written by WriteMessage)
output.WriteRawVarint32((uint)(nestedMessage.CalculateSize() + 3));
output.WriteTag(2, WireFormat.WireType.LengthDelimited);
output.WriteMessage(nestedMessage);
output.Flush();
var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
Assert.AreEqual(nestedMessage, parsed.MapInt32ForeignMessage[0]);
}
[Test]
public void MapIgnoresExtraFieldsWithinEntryMessages()
{
// Hand-craft the stream to contain a single entry with three fields
var memoryStream = new MemoryStream();
var output = CodedOutputStream.CreateInstance(memoryStream);
output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
var key = 10; // Field 1
var value = 20; // Field 2
var extra = 30; // Field 3
// Each field can be represented in a single byte, with a single byte tag.
// Total message size: 6 bytes.
output.WriteRawVarint32(6);
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key);
output.WriteTag(2, WireFormat.WireType.Varint);
output.WriteInt32(value);
output.WriteTag(3, WireFormat.WireType.Varint);
output.WriteInt32(extra);
output.Flush();
var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
Assert.AreEqual(value, parsed.MapInt32Int32[key]);
}
[Test]
public void MapFieldOrderIsIrrelevant()
{
var memoryStream = new MemoryStream();
var output = CodedOutputStream.CreateInstance(memoryStream);
output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
var key = 10;
var value = 20;
// Each field can be represented in a single byte, with a single byte tag.
// Total message size: 4 bytes.
output.WriteRawVarint32(4);
output.WriteTag(2, WireFormat.WireType.Varint);
output.WriteInt32(value);
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key);
output.Flush();
var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
Assert.AreEqual(value, parsed.MapInt32Int32[key]);
}
[Test]
public void MapNonContiguousEntries()
{
var memoryStream = new MemoryStream();
var output = CodedOutputStream.CreateInstance(memoryStream);
// Message structure:
// Entry for MapInt32Int32
// Entry for MapStringString
// Entry for MapInt32Int32
// First entry
var key1 = 10;
var value1 = 20;
output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
output.WriteRawVarint32(4);
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key1);
output.WriteTag(2, WireFormat.WireType.Varint);
output.WriteInt32(value1);
// Second entry
var key2 = "a";
var value2 = "b";
output.WriteTag(TestMap.MapStringStringFieldNumber, WireFormat.WireType.LengthDelimited);
output.WriteRawVarint32(6); // 3 bytes per entry: tag, size, character
output.WriteTag(1, WireFormat.WireType.LengthDelimited);
output.WriteString(key2);
output.WriteTag(2, WireFormat.WireType.LengthDelimited);
output.WriteString(value2);
// Third entry
var key3 = 15;
var value3 = 25;
output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
output.WriteRawVarint32(4);
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key3);
output.WriteTag(2, WireFormat.WireType.Varint);
output.WriteInt32(value3);
output.Flush();
var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
var expected = new TestMap
{
MapInt32Int32 = { { key1, value1 }, { key3, value3 } },
MapStringString = { { key2, value2 } }
};
Assert.AreEqual(expected, parsed);
}
[Test]
public void DuplicateKeys_LastEntryWins()
{
var memoryStream = new MemoryStream();
var output = CodedOutputStream.CreateInstance(memoryStream);
var key = 10;
var value1 = 20;
var value2 = 30;
// First entry
output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
output.WriteRawVarint32(4);
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key);
output.WriteTag(2, WireFormat.WireType.Varint);
output.WriteInt32(value1);
// Second entry - same key, different value
output.WriteTag(TestMap.MapInt32Int32FieldNumber, WireFormat.WireType.LengthDelimited);
output.WriteRawVarint32(4);
output.WriteTag(1, WireFormat.WireType.Varint);
output.WriteInt32(key);
output.WriteTag(2, WireFormat.WireType.Varint);
output.WriteInt32(value2);
output.Flush();
var parsed = TestMap.Parser.ParseFrom(memoryStream.ToArray());
Assert.AreEqual(value2, parsed.MapInt32Int32[key]);
}
[Test] [Test]
public void CloneSingleNonMessageValues() public void CloneSingleNonMessageValues()
{ {
......
...@@ -74,8 +74,12 @@ ...@@ -74,8 +74,12 @@
<Compile Include="ByteStringTest.cs" /> <Compile Include="ByteStringTest.cs" />
<Compile Include="CodedInputStreamTest.cs" /> <Compile Include="CodedInputStreamTest.cs" />
<Compile Include="CodedOutputStreamTest.cs" /> <Compile Include="CodedOutputStreamTest.cs" />
<Compile Include="EqualityTester.cs" />
<Compile Include="FieldCodecTest.cs" />
<Compile Include="GeneratedMessageTest.cs" /> <Compile Include="GeneratedMessageTest.cs" />
<Compile Include="RepeatedFieldTest.cs" /> <Compile Include="Collections\MapFieldTest.cs" />
<Compile Include="Collections\RepeatedFieldTest.cs" />
<Compile Include="TestProtos\MapUnittestProto3.cs" />
<Compile Include="TestProtos\UnittestImportProto3.cs" /> <Compile Include="TestProtos\UnittestImportProto3.cs" />
<Compile Include="TestProtos\UnittestImportPublicProto3.cs" /> <Compile Include="TestProtos\UnittestImportPublicProto3.cs" />
<Compile Include="TestProtos\UnittestIssues.cs" /> <Compile Include="TestProtos\UnittestIssues.cs" />
...@@ -99,9 +103,7 @@ ...@@ -99,9 +103,7 @@
<ItemGroup> <ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup />
<Folder Include="Collections\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
......
...@@ -120,7 +120,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -120,7 +120,7 @@ namespace Google.Protobuf.TestProtos {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
if (D != 0) hash ^= D.GetHashCode(); if (D != 0) hash ^= D.GetHashCode();
return hash; return hash;
} }
...@@ -139,6 +139,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -139,6 +139,7 @@ namespace Google.Protobuf.TestProtos {
} }
return size; return size;
} }
public void MergeFrom(ImportMessage other) { public void MergeFrom(ImportMessage other) {
if (other == null) { if (other == null) {
return; return;
......
...@@ -105,7 +105,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -105,7 +105,7 @@ namespace Google.Protobuf.TestProtos {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
if (E != 0) hash ^= E.GetHashCode(); if (E != 0) hash ^= E.GetHashCode();
return hash; return hash;
} }
...@@ -124,6 +124,7 @@ namespace Google.Protobuf.TestProtos { ...@@ -124,6 +124,7 @@ namespace Google.Protobuf.TestProtos {
} }
return size; return size;
} }
public void MergeFrom(PublicImportMessage other) { public void MergeFrom(PublicImportMessage other) {
if (other == null) { if (other == null) {
return; return;
......
...@@ -168,7 +168,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -168,7 +168,7 @@ namespace UnitTest.Issues.TestProtos {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
if (Value != global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO) hash ^= Value.GetHashCode(); if (Value != global::UnitTest.Issues.TestProtos.NegativeEnum.NEGATIVE_ENUM_ZERO) hash ^= Value.GetHashCode();
hash ^= values_.GetHashCode(); hash ^= values_.GetHashCode();
hash ^= packedValues_.GetHashCode(); hash ^= packedValues_.GetHashCode();
...@@ -212,6 +212,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -212,6 +212,7 @@ namespace UnitTest.Issues.TestProtos {
} }
return size; return size;
} }
public void MergeFrom(NegativeEnumMessage other) { public void MergeFrom(NegativeEnumMessage other) {
if (other == null) { if (other == null) {
return; return;
...@@ -303,7 +304,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -303,7 +304,7 @@ namespace UnitTest.Issues.TestProtos {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
return hash; return hash;
} }
...@@ -314,6 +315,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -314,6 +315,7 @@ namespace UnitTest.Issues.TestProtos {
int size = 0; int size = 0;
return size; return size;
} }
public void MergeFrom(DeprecatedChild other) { public void MergeFrom(DeprecatedChild other) {
if (other == null) { if (other == null) {
return; return;
...@@ -456,7 +458,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -456,7 +458,7 @@ namespace UnitTest.Issues.TestProtos {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
if (PrimitiveValue != 0) hash ^= PrimitiveValue.GetHashCode(); if (PrimitiveValue != 0) hash ^= PrimitiveValue.GetHashCode();
hash ^= primitiveArray_.GetHashCode(); hash ^= primitiveArray_.GetHashCode();
if (messageValue_ != null) hash ^= MessageValue.GetHashCode(); if (messageValue_ != null) hash ^= MessageValue.GetHashCode();
...@@ -527,6 +529,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -527,6 +529,7 @@ namespace UnitTest.Issues.TestProtos {
} }
return size; return size;
} }
public void MergeFrom(DeprecatedFieldsMessage other) { public void MergeFrom(DeprecatedFieldsMessage other) {
if (other == null) { if (other == null) {
return; return;
...@@ -655,7 +658,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -655,7 +658,7 @@ namespace UnitTest.Issues.TestProtos {
} }
public override int GetHashCode() { public override int GetHashCode() {
int hash = 0; int hash = 1;
if (Item != 0) hash ^= Item.GetHashCode(); if (Item != 0) hash ^= Item.GetHashCode();
return hash; return hash;
} }
...@@ -674,6 +677,7 @@ namespace UnitTest.Issues.TestProtos { ...@@ -674,6 +677,7 @@ namespace UnitTest.Issues.TestProtos {
} }
return size; return size;
} }
public void MergeFrom(ItemField other) { public void MergeFrom(ItemField other) {
if (other == null) { if (other == null) {
return; return;
......
...@@ -456,14 +456,16 @@ namespace Google.Protobuf ...@@ -456,14 +456,16 @@ namespace Google.Protobuf
} }
/// <summary> /// <summary>
/// Returns true if the next tag is also part of the same unpacked array. /// Peeks at the next tag in the stream. If it matches <paramref name="tag"/>,
/// the tag is consumed and the method returns <c>true</c>; otherwise, the
/// stream is left in the original position and the method returns <c>false</c>.
/// </summary> /// </summary>
private bool ContinueArray(uint currentTag) public bool MaybeConsumeTag(uint tag)
{ {
uint next; uint next;
if (PeekNextTag(out next)) if (PeekNextTag(out next))
{ {
if (next == currentTag) if (next == tag)
{ {
hasNextTag = false; hasNextTag = false;
return true; return true;
...@@ -486,17 +488,7 @@ namespace Google.Protobuf ...@@ -486,17 +488,7 @@ namespace Google.Protobuf
} }
return true; return true;
} }
return MaybeConsumeTag(currentTag);
uint next;
if (PeekNextTag(out next))
{
if (next == currentTag)
{
hasNextTag = false;
return true;
}
}
return false;
} }
/// <summary> /// <summary>
...@@ -512,7 +504,7 @@ namespace Google.Protobuf ...@@ -512,7 +504,7 @@ namespace Google.Protobuf
do do
{ {
list.Add(ReadString()); list.Add(ReadString());
} while (ContinueArray(fieldTag)); } while (MaybeConsumeTag(fieldTag));
} }
public void ReadBytesArray(ICollection<ByteString> list) public void ReadBytesArray(ICollection<ByteString> list)
...@@ -521,7 +513,7 @@ namespace Google.Protobuf ...@@ -521,7 +513,7 @@ namespace Google.Protobuf
do do
{ {
list.Add(ReadBytes()); list.Add(ReadBytes());
} while (ContinueArray(fieldTag)); } while (MaybeConsumeTag(fieldTag));
} }
public void ReadBoolArray(ICollection<bool> list) public void ReadBoolArray(ICollection<bool> list)
...@@ -729,7 +721,7 @@ namespace Google.Protobuf ...@@ -729,7 +721,7 @@ namespace Google.Protobuf
do do
{ {
list.Add((T)(object) ReadEnum()); list.Add((T)(object) ReadEnum());
} while (ContinueArray(fieldTag)); } while (MaybeConsumeTag(fieldTag));
} }
} }
...@@ -742,7 +734,7 @@ namespace Google.Protobuf ...@@ -742,7 +734,7 @@ namespace Google.Protobuf
T message = messageParser.CreateTemplate(); T message = messageParser.CreateTemplate();
ReadMessage(message); ReadMessage(message);
list.Add(message); list.Add(message);
} while (ContinueArray(fieldTag)); } while (MaybeConsumeTag(fieldTag));
} }
#endregion #endregion
......
...@@ -475,6 +475,14 @@ namespace Google.Protobuf ...@@ -475,6 +475,14 @@ namespace Google.Protobuf
WriteRawVarint32(WireFormat.MakeTag(fieldNumber, type)); WriteRawVarint32(WireFormat.MakeTag(fieldNumber, type));
} }
/// <summary>
/// Writes an already-encoded tag.
/// </summary>
public void WriteTag(uint tag)
{
WriteRawVarint32(tag);
}
/// <summary> /// <summary>
/// Writes the given single-byte tag directly to the stream. /// Writes the given single-byte tag directly to the stream.
/// </summary> /// </summary>
......
This diff is collapsed.
...@@ -193,7 +193,7 @@ namespace Google.Protobuf.Collections ...@@ -193,7 +193,7 @@ namespace Google.Protobuf.Collections
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 23; int hash = 0;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
hash = hash * 31 + array[i].GetHashCode(); hash = hash * 31 + array[i].GetHashCode();
......
using System;
using System.Collections.Generic;
namespace Google.Protobuf
{
/// <summary>
/// Factory methods for <see cref="FieldCodec{T}"/>.
/// </summary>
public static class FieldCodec
{
public static FieldCodec<string> ForString(uint tag)
{
return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag);
}
public static FieldCodec<ByteString> ForBytes(uint tag)
{
return new FieldCodec<ByteString>(input => input.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag);
}
public static FieldCodec<bool> ForBool(uint tag)
{
return new FieldCodec<bool>(input => input.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.ComputeBoolSize, tag);
}
public static FieldCodec<int> ForInt32(uint tag)
{
return new FieldCodec<int>(input => input.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag);
}
public static FieldCodec<int> ForSInt32(uint tag)
{
return new FieldCodec<int>(input => input.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag);
}
public static FieldCodec<uint> ForFixed32(uint tag)
{
return new FieldCodec<uint>(input => input.ReadFixed32(), (output, value) => output.WriteFixed32(value), CodedOutputStream.ComputeFixed32Size, tag);
}
public static FieldCodec<int> ForSFixed32(uint tag)
{
return new FieldCodec<int>(input => input.ReadSFixed32(), (output, value) => output.WriteSFixed32(value), CodedOutputStream.ComputeSFixed32Size, tag);
}
public static FieldCodec<uint> ForUInt32(uint tag)
{
return new FieldCodec<uint>(input => input.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag);
}
public static FieldCodec<long> ForInt64(uint tag)
{
return new FieldCodec<long>(input => input.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag);
}
public static FieldCodec<long> ForSInt64(uint tag)
{
return new FieldCodec<long>(input => input.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag);
}
public static FieldCodec<ulong> ForFixed64(uint tag)
{
return new FieldCodec<ulong>(input => input.ReadFixed64(), (output, value) => output.WriteFixed64(value), CodedOutputStream.ComputeFixed64Size, tag);
}
public static FieldCodec<long> ForSFixed64(uint tag)
{
return new FieldCodec<long>(input => input.ReadSFixed64(), (output, value) => output.WriteSFixed64(value), CodedOutputStream.ComputeSFixed64Size, tag);
}
public static FieldCodec<ulong> ForUInt64(uint tag)
{
return new FieldCodec<ulong>(input => input.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag);
}
public static FieldCodec<float> ForFloat(uint tag)
{
return new FieldCodec<float>(input => input.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.ComputeFloatSize, tag);
}
public static FieldCodec<double> ForDouble(uint tag)
{
return new FieldCodec<double>(input => input.ReadDouble(), (output, value) => output.WriteDouble(value), CodedOutputStream.ComputeDoubleSize, tag);
}
// Enums are tricky. We can probably use expression trees to build these delegates automatically,
// but it's easy to generate the code fdor it.
public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32)
{
return new FieldCodec<T>(input => fromInt32(
input.ReadEnum()),
(output, value) => output.WriteEnum(toInt32(value)),
value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag);
}
public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : IMessage<T>
{
return new FieldCodec<T>(input => { T message = parser.CreateTemplate(); input.ReadMessage(message); return message; },
(output, value) => output.WriteMessage(value), message => CodedOutputStream.ComputeMessageSize(message), tag);
}
}
/// <summary>
/// An encode/decode pair for a single field. This effectively encapsulates
/// all the information needed to read or write the field value from/to a coded
/// stream.
/// </summary>
/// <remarks>
/// This never writes default values to the stream, and is not currently designed
/// to play well with packed arrays.
/// </remarks>
public sealed class FieldCodec<T>
{
private static readonly Func<T, bool> IsDefault;
private static readonly T Default;
static FieldCodec()
{
if (typeof(T) == typeof(string))
{
Default = (T)(object)"";
IsDefault = CreateDefaultValueCheck<string>(x => x.Length == 0);
}
else if (typeof(T) == typeof(ByteString))
{
Default = (T)(object)ByteString.Empty;
IsDefault = CreateDefaultValueCheck<ByteString>(x => x.Length == 0);
}
else if (!typeof(T).IsValueType)
{
// Default default
IsDefault = CreateDefaultValueCheck<T>(x => x == null);
}
else
{
// Default default
IsDefault = CreateDefaultValueCheck<T>(x => EqualityComparer<T>.Default.Equals(x, default(T)));
}
}
private static Func<T, bool> CreateDefaultValueCheck<TTmp>(Func<TTmp, bool> check)
{
return (Func<T, bool>)(object)check;
}
private readonly Func<CodedInputStream, T> reader;
private readonly Action<CodedOutputStream, T> writer;
private readonly Func<T, int> sizeComputer;
private readonly uint tag;
private readonly int tagSize;
internal FieldCodec(
Func<CodedInputStream, T> reader,
Action<CodedOutputStream, T> writer,
Func<T, int> sizeComputer,
uint tag)
{
this.reader = reader;
this.writer = writer;
this.sizeComputer = sizeComputer;
this.tag = tag;
tagSize = CodedOutputStream.ComputeRawVarint32Size(tag);
}
public uint Tag { get { return tag; } }
public T DefaultValue { get { return Default; } }
public void Write(CodedOutputStream output, T value)
{
if (!IsDefault(value))
{
output.WriteTag(tag);
writer(output, value);
}
}
public T Read(CodedInputStream input)
{
return reader(input);
}
public int CalculateSize(T value)
{
return IsDefault(value) ? 0 : sizeComputer(value) + CodedOutputStream.ComputeRawVarint32Size(tag);
}
}
}
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
<Compile Include="CodedOutputStream.cs" /> <Compile Include="CodedOutputStream.cs" />
<Compile Include="Collections\Dictionaries.cs" /> <Compile Include="Collections\Dictionaries.cs" />
<Compile Include="Collections\Lists.cs" /> <Compile Include="Collections\Lists.cs" />
<Compile Include="Collections\MapField.cs" />
<Compile Include="Collections\ReadOnlyDictionary.cs" /> <Compile Include="Collections\ReadOnlyDictionary.cs" />
<Compile Include="Collections\RepeatedField.cs" /> <Compile Include="Collections\RepeatedField.cs" />
<Compile Include="Collections\RepeatedFieldExtensions.cs" /> <Compile Include="Collections\RepeatedFieldExtensions.cs" />
...@@ -84,6 +85,7 @@ ...@@ -84,6 +85,7 @@
<Compile Include="Descriptors\MethodDescriptor.cs" /> <Compile Include="Descriptors\MethodDescriptor.cs" />
<Compile Include="Descriptors\PackageDescriptor.cs" /> <Compile Include="Descriptors\PackageDescriptor.cs" />
<Compile Include="Descriptors\ServiceDescriptor.cs" /> <Compile Include="Descriptors\ServiceDescriptor.cs" />
<Compile Include="FieldCodec.cs" />
<Compile Include="FrameworkPortability.cs" /> <Compile Include="FrameworkPortability.cs" />
<Compile Include="Freezable.cs" /> <Compile Include="Freezable.cs" />
<Compile Include="MessageExtensions.cs" /> <Compile Include="MessageExtensions.cs" />
......
...@@ -425,6 +425,8 @@ libprotoc_la_SOURCES = \ ...@@ -425,6 +425,8 @@ libprotoc_la_SOURCES = \
google/protobuf/compiler/csharp/csharp_generator.cc \ google/protobuf/compiler/csharp/csharp_generator.cc \
google/protobuf/compiler/csharp/csharp_helpers.cc \ google/protobuf/compiler/csharp/csharp_helpers.cc \
google/protobuf/compiler/csharp/csharp_helpers.h \ google/protobuf/compiler/csharp/csharp_helpers.h \
google/protobuf/compiler/csharp/csharp_map_field.cc \
google/protobuf/compiler/csharp/csharp_map_field.h \
google/protobuf/compiler/csharp/csharp_message.cc \ google/protobuf/compiler/csharp/csharp_message.cc \
google/protobuf/compiler/csharp/csharp_message.h \ google/protobuf/compiler/csharp/csharp_message.h \
google/protobuf/compiler/csharp/csharp_message_field.cc \ google/protobuf/compiler/csharp/csharp_message_field.cc \
......
...@@ -74,6 +74,12 @@ void EnumFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) { ...@@ -74,6 +74,12 @@ void EnumFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
"}\n"); "}\n");
} }
void EnumFieldGenerator::GenerateCodecCode(io::Printer* printer) {
printer->Print(
variables_,
"pb::FieldCodec.ForEnum($tag$, x => (int) x, x => ($type_name$) x)");
}
EnumOneofFieldGenerator::EnumOneofFieldGenerator(const FieldDescriptor* descriptor, EnumOneofFieldGenerator::EnumOneofFieldGenerator(const FieldDescriptor* descriptor,
int fieldOrdinal) int fieldOrdinal)
: PrimitiveOneofFieldGenerator(descriptor, fieldOrdinal) { : PrimitiveOneofFieldGenerator(descriptor, fieldOrdinal) {
......
...@@ -46,6 +46,7 @@ class EnumFieldGenerator : public PrimitiveFieldGenerator { ...@@ -46,6 +46,7 @@ class EnumFieldGenerator : public PrimitiveFieldGenerator {
EnumFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal); EnumFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
~EnumFieldGenerator(); ~EnumFieldGenerator();
virtual void GenerateCodecCode(io::Printer* printer);
virtual void GenerateParsingCode(io::Printer* printer); virtual void GenerateParsingCode(io::Printer* printer);
virtual void GenerateSerializationCode(io::Printer* printer); virtual void GenerateSerializationCode(io::Printer* printer);
virtual void GenerateSerializedSizeCode(io::Printer* printer); virtual void GenerateSerializedSizeCode(io::Printer* printer);
......
...@@ -65,6 +65,7 @@ void FieldGeneratorBase::SetCommonFieldVariables( ...@@ -65,6 +65,7 @@ void FieldGeneratorBase::SetCommonFieldVariables(
tag_bytes += ", " + SimpleItoa(tag_array[i]); tag_bytes += ", " + SimpleItoa(tag_array[i]);
} }
(*variables)["tag"] = SimpleItoa(tag);
(*variables)["tag_size"] = SimpleItoa(tag_size); (*variables)["tag_size"] = SimpleItoa(tag_size);
(*variables)["tag_bytes"] = tag_bytes; (*variables)["tag_bytes"] = tag_bytes;
...@@ -112,6 +113,11 @@ void FieldGeneratorBase::GenerateFreezingCode(io::Printer* printer) { ...@@ -112,6 +113,11 @@ void FieldGeneratorBase::GenerateFreezingCode(io::Printer* printer) {
// special handling for freezing, so default to not generating any code. // special handling for freezing, so default to not generating any code.
} }
void FieldGeneratorBase::GenerateCodecCode(io::Printer* printer) {
// No-op: expect this to be overridden by appropriate types.
// Could fail if we get called here though...
}
void FieldGeneratorBase::AddDeprecatedFlag(io::Printer* printer) { void FieldGeneratorBase::AddDeprecatedFlag(io::Printer* printer) {
if (descriptor_->options().deprecated()) if (descriptor_->options().deprecated())
{ {
...@@ -151,12 +157,16 @@ std::string FieldGeneratorBase::name() { ...@@ -151,12 +157,16 @@ std::string FieldGeneratorBase::name() {
} }
std::string FieldGeneratorBase::type_name() { std::string FieldGeneratorBase::type_name() {
switch (descriptor_->type()) { return type_name(descriptor_);
}
std::string FieldGeneratorBase::type_name(const FieldDescriptor* descriptor) {
switch (descriptor->type()) {
case FieldDescriptor::TYPE_ENUM: case FieldDescriptor::TYPE_ENUM:
return GetClassName(descriptor_->enum_type()); return GetClassName(descriptor->enum_type());
case FieldDescriptor::TYPE_MESSAGE: case FieldDescriptor::TYPE_MESSAGE:
case FieldDescriptor::TYPE_GROUP: case FieldDescriptor::TYPE_GROUP:
return GetClassName(descriptor_->message_type()); return GetClassName(descriptor->message_type());
case FieldDescriptor::TYPE_DOUBLE: case FieldDescriptor::TYPE_DOUBLE:
return "double"; return "double";
case FieldDescriptor::TYPE_FLOAT: case FieldDescriptor::TYPE_FLOAT:
......
...@@ -49,6 +49,7 @@ class FieldGeneratorBase : public SourceGeneratorBase { ...@@ -49,6 +49,7 @@ class FieldGeneratorBase : public SourceGeneratorBase {
virtual void GenerateCloningCode(io::Printer* printer) = 0; virtual void GenerateCloningCode(io::Printer* printer) = 0;
virtual void GenerateFreezingCode(io::Printer* printer); virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateCodecCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer) = 0; virtual void GenerateMembers(io::Printer* printer) = 0;
virtual void GenerateMergingCode(io::Printer* printer) = 0; virtual void GenerateMergingCode(io::Printer* printer) = 0;
virtual void GenerateParsingCode(io::Printer* printer) = 0; virtual void GenerateParsingCode(io::Printer* printer) = 0;
...@@ -76,6 +77,7 @@ class FieldGeneratorBase : public SourceGeneratorBase { ...@@ -76,6 +77,7 @@ class FieldGeneratorBase : public SourceGeneratorBase {
std::string property_name(); std::string property_name();
std::string name(); std::string name();
std::string type_name(); std::string type_name();
std::string type_name(const FieldDescriptor* descriptor);
bool has_default_value(); bool has_default_value();
bool is_nullable_type(); bool is_nullable_type();
std::string default_value(); std::string default_value();
......
...@@ -46,6 +46,7 @@ ...@@ -46,6 +46,7 @@
#include <google/protobuf/compiler/csharp/csharp_field_base.h> #include <google/protobuf/compiler/csharp/csharp_field_base.h>
#include <google/protobuf/compiler/csharp/csharp_enum_field.h> #include <google/protobuf/compiler/csharp/csharp_enum_field.h>
#include <google/protobuf/compiler/csharp/csharp_map_field.h>
#include <google/protobuf/compiler/csharp/csharp_message_field.h> #include <google/protobuf/compiler/csharp/csharp_message_field.h>
#include <google/protobuf/compiler/csharp/csharp_primitive_field.h> #include <google/protobuf/compiler/csharp/csharp_primitive_field.h>
#include <google/protobuf/compiler/csharp/csharp_repeated_enum_field.h> #include <google/protobuf/compiler/csharp/csharp_repeated_enum_field.h>
...@@ -355,7 +356,11 @@ FieldGeneratorBase* CreateFieldGenerator(const FieldDescriptor* descriptor, ...@@ -355,7 +356,11 @@ FieldGeneratorBase* CreateFieldGenerator(const FieldDescriptor* descriptor,
case FieldDescriptor::TYPE_GROUP: case FieldDescriptor::TYPE_GROUP:
case FieldDescriptor::TYPE_MESSAGE: case FieldDescriptor::TYPE_MESSAGE:
if (descriptor->is_repeated()) { if (descriptor->is_repeated()) {
return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal); if (descriptor->is_map()) {
return new MapFieldGenerator(descriptor, fieldOrdinal);
} else {
return new RepeatedMessageFieldGenerator(descriptor, fieldOrdinal);
}
} else { } else {
if (descriptor->containing_oneof()) { if (descriptor->containing_oneof()) {
return new MessageOneofFieldGenerator(descriptor, fieldOrdinal); return new MessageOneofFieldGenerator(descriptor, fieldOrdinal);
......
// Protocol Buffers - Google's data interchange format
// Copyright 2015 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.
#include <sstream>
#include <google/protobuf/compiler/code_generator.h>
#include <google/protobuf/compiler/plugin.h>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/stubs/strutil.h>
#include <google/protobuf/compiler/csharp/csharp_helpers.h>
#include <google/protobuf/compiler/csharp/csharp_map_field.h>
namespace google {
namespace protobuf {
namespace compiler {
namespace csharp {
MapFieldGenerator::MapFieldGenerator(const FieldDescriptor* descriptor,
int fieldOrdinal)
: FieldGeneratorBase(descriptor, fieldOrdinal) {
}
MapFieldGenerator::~MapFieldGenerator() {
}
void MapFieldGenerator::GenerateMembers(io::Printer* printer) {
const FieldDescriptor* key_descriptor =
descriptor_->message_type()->FindFieldByName("key");
const FieldDescriptor* value_descriptor =
descriptor_->message_type()->FindFieldByName("value");
variables_["key_type_name"] = type_name(key_descriptor);
variables_["value_type_name"] = type_name(value_descriptor);
scoped_ptr<FieldGeneratorBase> key_generator(CreateFieldGenerator(key_descriptor, 1));
scoped_ptr<FieldGeneratorBase> value_generator(CreateFieldGenerator(value_descriptor, 2));
printer->Print(
variables_,
"private static readonly pbc::MapField<$key_type_name$, $value_type_name$>.Codec _map_$name$_codec\n"
" = new pbc::MapField<$key_type_name$, $value_type_name$>.Codec(");
key_generator->GenerateCodecCode(printer);
printer->Print(", ");
value_generator->GenerateCodecCode(printer);
printer->Print(
variables_,
", $tag$);\n"
"private readonly pbc::MapField<$key_type_name$, $value_type_name$> $name$_ = new pbc::MapField<$key_type_name$, $value_type_name$>();\n");
AddDeprecatedFlag(printer);
printer->Print(
variables_,
"public pbc::MapField<$key_type_name$, $value_type_name$> $property_name$ {\n"
" get { return $name$_; }\n"
"}\n");
}
void MapFieldGenerator::GenerateMergingCode(io::Printer* printer) {
printer->Print(
variables_,
"$name$_.Add(other.$name$_);\n");
}
void MapFieldGenerator::GenerateParsingCode(io::Printer* printer) {
printer->Print(
variables_,
"$name$_.AddEntriesFrom(input, _map_$name$_codec);\n");
}
void MapFieldGenerator::GenerateSerializationCode(io::Printer* printer) {
printer->Print(
variables_,
"$name$_.WriteTo(output, _map_$name$_codec);\n");
}
void MapFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
printer->Print(
variables_,
"size += $name$_.CalculateSize(_map_$name$_codec);\n");
}
void MapFieldGenerator::WriteHash(io::Printer* printer) {
printer->Print(
variables_,
"hash ^= $property_name$.GetHashCode();\n");
}
void MapFieldGenerator::WriteEquals(io::Printer* printer) {
printer->Print(
variables_,
"if (!$property_name$.Equals(other.$property_name$)) return false;\n");
}
void MapFieldGenerator::WriteToString(io::Printer* printer) {
/*
variables_["field_name"] = GetFieldName(descriptor_);
printer->Print(
variables_,
"PrintField(\"$field_name$\", has$property_name$, $name$_, writer);\n");*/
}
void MapFieldGenerator::GenerateCloningCode(io::Printer* printer) {
printer->Print(variables_,
"$name$_ = other.$name$_.Clone();\n");
}
void MapFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
printer->Print(variables_,
"$name$_.Freeze();\n");
}
} // namespace csharp
} // namespace compiler
} // namespace protobuf
} // namespace google
// 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.
#ifndef GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__
#define GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__
#include <string>
#include <google/protobuf/compiler/code_generator.h>
#include <google/protobuf/compiler/csharp/csharp_field_base.h>
namespace google {
namespace protobuf {
namespace compiler {
namespace csharp {
class MapFieldGenerator : public FieldGeneratorBase {
public:
MapFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
~MapFieldGenerator();
virtual void GenerateCloningCode(io::Printer* printer);
virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer);
virtual void GenerateMergingCode(io::Printer* printer);
virtual void GenerateParsingCode(io::Printer* printer);
virtual void GenerateSerializationCode(io::Printer* printer);
virtual void GenerateSerializedSizeCode(io::Printer* printer);
virtual void WriteHash(io::Printer* printer);
virtual void WriteEquals(io::Printer* printer);
virtual void WriteToString(io::Printer* printer);
private:
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MapFieldGenerator);
};
} // namespace csharp
} // namespace compiler
} // namespace protobuf
} // namespace google
#endif // GOOGLE_PROTOBUF_COMPILER_CSHARP_MAP_FIELD_H__
...@@ -268,8 +268,6 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -268,8 +268,6 @@ void MessageGenerator::Generate(io::Printer* printer) {
"}\n\n"); "}\n\n");
} }
// TODO(jonskeet): Map properties
// Standard methods // Standard methods
GenerateFrameworkMethods(printer); GenerateFrameworkMethods(printer);
GenerateMessageSerializationMethods(printer); GenerateMessageSerializationMethods(printer);
...@@ -299,7 +297,6 @@ void MessageGenerator::Generate(io::Printer* printer) { ...@@ -299,7 +297,6 @@ void MessageGenerator::Generate(io::Printer* printer) {
printer->Outdent(); printer->Outdent();
printer->Print("}\n"); printer->Print("}\n");
printer->Print("\n"); printer->Print("\n");
} }
void MessageGenerator::GenerateCloningCode(io::Printer* printer) { void MessageGenerator::GenerateCloningCode(io::Printer* printer) {
...@@ -408,9 +405,10 @@ void MessageGenerator::GenerateFrameworkMethods(io::Printer* printer) { ...@@ -408,9 +405,10 @@ void MessageGenerator::GenerateFrameworkMethods(io::Printer* printer) {
"}\n\n"); "}\n\n");
// GetHashCode // GetHashCode
// Start with a non-zero value to easily distinguish between null and "empty" messages.
printer->Print( printer->Print(
"public override int GetHashCode() {\n" "public override int GetHashCode() {\n"
" int hash = 0;\n"); " int hash = 1;\n");
printer->Indent(); printer->Indent();
for (int i = 0; i < descriptor_->field_count(); i++) { for (int i = 0; i < descriptor_->field_count(); i++) {
scoped_ptr<FieldGeneratorBase> generator( scoped_ptr<FieldGeneratorBase> generator(
...@@ -451,7 +449,7 @@ void MessageGenerator::GenerateMessageSerializationMethods(io::Printer* printer) ...@@ -451,7 +449,7 @@ void MessageGenerator::GenerateMessageSerializationMethods(io::Printer* printer)
} }
printer->Print("return size;\n"); printer->Print("return size;\n");
printer->Outdent(); printer->Outdent();
printer->Print("}\n"); printer->Print("}\n\n");
} }
void MessageGenerator::GenerateMergingMethods(io::Printer* printer) { void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
...@@ -469,7 +467,6 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) { ...@@ -469,7 +467,6 @@ void MessageGenerator::GenerateMergingMethods(io::Printer* printer) {
"if (other == null) {\n" "if (other == null) {\n"
" return;\n" " return;\n"
"}\n"); "}\n");
// TODO(jonskeet): Maps?
// Merge non-oneof fields // Merge non-oneof fields
for (int i = 0; i < descriptor_->field_count(); i++) { for (int i = 0; i < descriptor_->field_count(); i++) {
if (!descriptor_->field(i)->containing_oneof()) { if (!descriptor_->field(i)->containing_oneof()) {
......
...@@ -138,6 +138,12 @@ void MessageFieldGenerator::GenerateFreezingCode(io::Printer* printer) { ...@@ -138,6 +138,12 @@ void MessageFieldGenerator::GenerateFreezingCode(io::Printer* printer) {
"if ($has_property_check$) $property_name$.Freeze();\n"); "if ($has_property_check$) $property_name$.Freeze();\n");
} }
void MessageFieldGenerator::GenerateCodecCode(io::Printer* printer) {
printer->Print(
variables_,
"pb::FieldCodec.ForMessage($tag$, $type_name$.Parser)");
}
MessageOneofFieldGenerator::MessageOneofFieldGenerator(const FieldDescriptor* descriptor, MessageOneofFieldGenerator::MessageOneofFieldGenerator(const FieldDescriptor* descriptor,
int fieldOrdinal) int fieldOrdinal)
: MessageFieldGenerator(descriptor, fieldOrdinal) { : MessageFieldGenerator(descriptor, fieldOrdinal) {
......
...@@ -46,6 +46,7 @@ class MessageFieldGenerator : public FieldGeneratorBase { ...@@ -46,6 +46,7 @@ class MessageFieldGenerator : public FieldGeneratorBase {
MessageFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal); MessageFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
~MessageFieldGenerator(); ~MessageFieldGenerator();
virtual void GenerateCodecCode(io::Printer* printer);
virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer);
virtual void GenerateFreezingCode(io::Printer* printer); virtual void GenerateFreezingCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer);
......
...@@ -129,7 +129,7 @@ void PrimitiveFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) { ...@@ -129,7 +129,7 @@ void PrimitiveFieldGenerator::GenerateSerializedSizeCode(io::Printer* printer) {
"size += $tag_size$ + $fixed_size$;\n", "size += $tag_size$ + $fixed_size$;\n",
"fixed_size", SimpleItoa(fixedSize), "fixed_size", SimpleItoa(fixedSize),
"tag_size", variables_["tag_size"]); "tag_size", variables_["tag_size"]);
} }
printer->Outdent(); printer->Outdent();
printer->Print("}\n"); printer->Print("}\n");
} }
...@@ -155,6 +155,12 @@ void PrimitiveFieldGenerator::GenerateCloningCode(io::Printer* printer) { ...@@ -155,6 +155,12 @@ void PrimitiveFieldGenerator::GenerateCloningCode(io::Printer* printer) {
"$name$_ = other.$name$_;\n"); "$name$_ = other.$name$_;\n");
} }
void PrimitiveFieldGenerator::GenerateCodecCode(io::Printer* printer) {
printer->Print(
variables_,
"pb::FieldCodec.For$capitalized_type_name$($tag$)");
}
PrimitiveOneofFieldGenerator::PrimitiveOneofFieldGenerator( PrimitiveOneofFieldGenerator::PrimitiveOneofFieldGenerator(
const FieldDescriptor* descriptor, int fieldOrdinal) const FieldDescriptor* descriptor, int fieldOrdinal)
: PrimitiveFieldGenerator(descriptor, fieldOrdinal) { : PrimitiveFieldGenerator(descriptor, fieldOrdinal) {
......
...@@ -46,6 +46,7 @@ class PrimitiveFieldGenerator : public FieldGeneratorBase { ...@@ -46,6 +46,7 @@ class PrimitiveFieldGenerator : public FieldGeneratorBase {
PrimitiveFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal); PrimitiveFieldGenerator(const FieldDescriptor* descriptor, int fieldOrdinal);
~PrimitiveFieldGenerator(); ~PrimitiveFieldGenerator();
virtual void GenerateCodecCode(io::Printer* printer);
virtual void GenerateCloningCode(io::Printer* printer); virtual void GenerateCloningCode(io::Printer* printer);
virtual void GenerateMembers(io::Printer* printer); virtual void GenerateMembers(io::Printer* printer);
virtual void GenerateMergingCode(io::Printer* printer); virtual void GenerateMergingCode(io::Printer* printer);
......
// 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.
// This file is mostly equivalent to map_unittest.proto, but imports
// unittest_proto3.proto instead of unittest.proto, so that it only
// uses proto3 messages. This makes it suitable for testing
// implementations which only support proto3.
// The TestRequiredMessageMap message has been removed as there are no
// required fields in proto3.
syntax = "proto3";
option cc_enable_arenas = true;
option csharp_namespace = "Google.Protobuf.TestProtos";
import "google/protobuf/unittest_proto3.proto";
// We don't put this in a package within proto2 because we need to make sure
// that the generated code doesn't depend on being in the proto2 namespace.
// In map_test_util.h we do "using namespace unittest = protobuf_unittest".
package protobuf_unittest;
// Tests maps.
message TestMap {
map<int32 , int32 > map_int32_int32 = 1;
map<int64 , int64 > map_int64_int64 = 2;
map<uint32 , uint32 > map_uint32_uint32 = 3;
map<uint64 , uint64 > map_uint64_uint64 = 4;
map<sint32 , sint32 > map_sint32_sint32 = 5;
map<sint64 , sint64 > map_sint64_sint64 = 6;
map<fixed32 , fixed32 > map_fixed32_fixed32 = 7;
map<fixed64 , fixed64 > map_fixed64_fixed64 = 8;
map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 9;
map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 10;
map<int32 , float > map_int32_float = 11;
map<int32 , double > map_int32_double = 12;
map<bool , bool > map_bool_bool = 13;
map<string , string > map_string_string = 14;
map<int32 , bytes > map_int32_bytes = 15;
map<int32 , MapEnum > map_int32_enum = 16;
map<int32 , ForeignMessage> map_int32_foreign_message = 17;
}
message TestMapSubmessage {
TestMap test_map = 1;
}
message TestMessageMap {
map<int32, TestAllTypes> map_int32_message = 1;
}
// Two map fields share the same entry default instance.
message TestSameTypeMap {
map<int32, int32> map1 = 1;
map<int32, int32> map2 = 2;
}
enum MapEnum {
MAP_ENUM_FOO = 0;
MAP_ENUM_BAR = 1;
MAP_ENUM_BAZ = 2;
}
message TestArenaMap {
map<int32 , int32 > map_int32_int32 = 1;
map<int64 , int64 > map_int64_int64 = 2;
map<uint32 , uint32 > map_uint32_uint32 = 3;
map<uint64 , uint64 > map_uint64_uint64 = 4;
map<sint32 , sint32 > map_sint32_sint32 = 5;
map<sint64 , sint64 > map_sint64_sint64 = 6;
map<fixed32 , fixed32 > map_fixed32_fixed32 = 7;
map<fixed64 , fixed64 > map_fixed64_fixed64 = 8;
map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 9;
map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 10;
map<int32 , float > map_int32_float = 11;
map<int32 , double > map_int32_double = 12;
map<bool , bool > map_bool_bool = 13;
map<int32 , MapEnum > map_int32_enum = 14;
map<int32 , ForeignMessage> map_int32_foreign_message = 15;
}
// Previously, message containing enum called Type cannot be used as value of
// map field.
message MessageContainingEnumCalledType {
enum Type {
TYPE_FOO = 0;
}
map<int32, MessageContainingEnumCalledType> type = 1;
}
// Previously, message cannot contain map field called "entry".
message MessageContainingMapCalledEntry {
map<int32, int32> entry = 1;
}
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